/**
 * Takes a token stream from the lexer, and parses it into an abstract syntax tree.
 *
 * Specification: C11
 *
 * Copyright:   Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved
 * Authors:     $(LINK2 https://www.digitalmars.com, Walter Bright)
 * License:     $(LINK2 https://www.boost.org/LICENSE_1_0.txt, Boost License 1.0)
 * Source:      $(LINK2 https://github.com/dlang/dmd/blob/master/src/dmd/cparse.d, _cparse.d)
 * Documentation:  https://dlang.org/phobos/dmd_cparse.html
 * Coverage:    https://codecov.io/gh/dlang/dmd/src/master/src/dmd/cparse.d
 */

module dmd.cparse;

import core.stdc.stdio;
import core.stdc.string : memcpy;

import dmd.astenums;
import dmd.errorsink;
import dmd.id;
import dmd.identifier;
import dmd.lexer;
import dmd.location;
import dmd.parse;
import dmd.root.array;
import dmd.common.outbuffer;
import dmd.root.rmem;
import dmd.tokens;
import dmd.typesem : size;

/***********************************************************
 */
final class CParser(AST) : Parser!AST
{
    AST.Dsymbols* symbols;      // symbols declared in current scope

    bool addFuncName;           /// add declaration of __func__ to function symbol table
    bool importBuiltins;        /// seen use of C compiler builtins, so import __importc_builtins;

    private
    {
        structalign_t packalign;        // current state of #pragma pack alignment

        // #pragma pack stack
        Array!Identifier* records;      // identifers (or null)
        Array!structalign_t* packs;     // parallel alignment values

        STC defaultStorageClasses;
        Array!STC* defaultStorageClassesStack;
    }

    /* C cannot be parsed without determining if an identifier is a type or a variable.
     * For expressions like `(T)-3`, is it a cast or a minus expression?
     * It also occurs with `typedef int (F)(); F fun;`
     * but to build the AST we need to distinguish `fun` being a function as opposed to a variable.
     * To fix, build a symbol table for the typedefs.
     * Symbol table of typedefs indexed by Identifier cast to void*.
     * 1. if an identifier is a typedef, then it will return a non-null Type
     * 2. if an identifier is not a typedef, then it will return null
     */
    Array!(void*) typedefTab;  /// Array of AST.Type[Identifier], typedef's indexed by Identifier

    /* This is passed in as a list of #define lines, as generated by the C preprocessor with the
     * appropriate switch to emit them. We append to it any #define's and #undef's encountered in the source
     * file, as cpp with the -dD embeds them in the preprocessed output file.
     * Once the file is parsed, then the #define's are converted to D symbols and appended to the array
     * of Dsymbols returned by parseModule().
     */
    OutBuffer* defines;

    extern (D) this(TARGET)(AST.Module _module, const(char)[] input, bool doDocComment,
                            ErrorSink errorSink,
                            const ref TARGET target, OutBuffer* defines, const CompileEnv* compileEnv) scope
    {
        const bool doUnittests = false;
        super(_module, input, doDocComment, errorSink, compileEnv, doUnittests);

        //printf("CParser.this()\n");
        mod = _module;
        linkage = LINK.c;
        Ccompile = true;
        this.packalign.setDefault();
        this.defines = defines;

        // Configure sizes for C `long`, `long double`, `wchar_t`, ...
        this.boolsize = target.boolsize;
        this.shortsize = target.shortsize;
        this.intsize = target.intsize;
        this.longsize = target.longsize;
        this.long_longsize = target.long_longsize;
        this.long_doublesize = target.long_doublesize;
        this.wchar_tsize = target.wchar_tsize;

        // C `char` is always unsigned in ImportC

        // We know that we are parsing out C, due the parent not knowing this, we have to setup tables here.
        charLookup = compileEnv.cCharLookupTable;
    }

    /********************************************
     * Parse translation unit.
     * C11 6.9
     * translation-unit:
     *    external-declaration
     *    translation-unit external-declaration
     *
     * external-declaration:
     *    function-definition
     *    declaration
     * Returns:
     *  array of Dsymbols that were declared
     */
    override AST.Dsymbols* parseModule()
    {
        //printf("cparseTranslationUnit()\n");
        symbols = new AST.Dsymbols();
        typedefTab.push(null);  // C11 6.2.1-3 symbol table for "file scope"
        while (1)
        {
            if (token.value == TOK.endOfFile)
            {
                addDefines();   // convert #define's to Dsymbols

                // wrap the symbols in `extern (C) { symbols }`
                auto wrap = new AST.Dsymbols();
                auto ld = new AST.LinkDeclaration(token.loc, LINK.c, symbols);
                wrap.push(ld);

                if (importBuiltins)
                {
                    /* Seen references to C builtin functions.
                     * Import their definitions
                     */
                    auto s = new AST.Import(Loc.initial, null, Id.importc_builtins, null, false);
                    wrap.push(s);
                }

                // end of file scope
                typedefTab.pop();
                assert(typedefTab.length == 0);

                return wrap;
            }

            /* GNU Extensions
             * external-declaration:
             *    simple-asm-expr ;
             */
            if (token.value == TOK.asm_)
            {
                nextToken();     // move past asm
                check(TOK.leftParenthesis);
                if (token.value != TOK.string_)
                    error("string literal expected for Asm Definition, not `%s`", token.toChars());
                auto code = cparsePrimaryExp();
                check(TOK.rightParenthesis);
                symbols.push(new AST.CAsmDeclaration(code));
                check(TOK.semicolon);
                continue;
            }

            cparseDeclaration(LVL.global);
        }
    }

    /******************************************************************************/
    /********************************* Statement Parser ***************************/
    //{

    /**********************
     * C11 6.8
     * statement:
     *    labeled-statement
     *    compound-statement
     *    expression-statement
     *    selection-statement
     *    iteration-statement
     *    jump-statement
     *
     * Params:
     *      flags = PSxxxx
     *      endPtr = store location of closing brace
     *      pEndloc = if { ... statements ... }, store location of closing brace, otherwise loc of last token of statement
     * Returns:
     *      parsed statement
     */
    AST.Statement cparseStatement(int flags, const(char)** endPtr = null, Loc* pEndloc = null)
    {
        AST.Statement s;
        const loc = token.loc;

        //printf("cparseStatement()\n");

        const typedefTabLengthSave = typedefTab.length;
        auto symbolsSave = symbols;
        if (flags & ParseStatementFlags.scope_)
        {
            typedefTab.push(null);      // introduce new block scope
        }

        if (!(flags & (ParseStatementFlags.scope_ | ParseStatementFlags.curlyScope)))
        {
            symbols = new AST.Dsymbols();
        }

        switch (token.value)
        {
        case TOK.identifier:
            /* A leading identifier can be a declaration, label, or expression.
             * A quick check of the next token can disambiguate most cases.
             */
            switch (peekNext())
            {
                case TOK.colon:
                {
                    // It's a label
                    auto ident = token.ident;
                    nextToken();    // advance to `:`
                    nextToken();    // advance past `:`
                    if (token.value == TOK.rightCurly)
                        s = null;
                    else if (token.value == TOK.leftCurly)
                        s = cparseStatement(ParseStatementFlags.curly | ParseStatementFlags.scope_);
                    else
                        s = cparseStatement(0);
                    s = new AST.LabelStatement(loc, ident, s);
                    break;
                }

                case TOK.dot:
                case TOK.arrow:
                case TOK.plusPlus:
                case TOK.minusMinus:
                case TOK.leftBracket:
                case TOK.question:
                case TOK.assign:
                case TOK.addAssign:
                case TOK.minAssign:
                case TOK.mulAssign:
                case TOK.divAssign:
                case TOK.modAssign:
                case TOK.andAssign:
                case TOK.orAssign:
                case TOK.xorAssign:
                case TOK.leftShiftAssign:
                case TOK.rightShiftAssign:
                    goto Lexp;

                case TOK.leftParenthesis:
                    if (auto pt = lookupTypedef(token.ident))
                    {
                        if (*pt)
                            goto Ldeclaration;
                    }
                    goto Lexp;  // function call

                case TOK.semicolon:
                    goto Lexp;

                default:
                {
                    /* If tokens look like a declaration, assume it is one
                     */
                    auto tk = &token;
                    if (isCDeclaration(tk))
                        goto Ldeclaration;
                    goto Lexp;
                }
            }
            break;

        case TOK.charLiteral:
        case TOK.wcharLiteral:
        case TOK.dcharLiteral:
        case TOK.int32Literal:
        case TOK.uns32Literal:
        case TOK.int64Literal:
        case TOK.uns64Literal:
        case TOK.int128Literal:
        case TOK.uns128Literal:
        case TOK.float32Literal:
        case TOK.float64Literal:
        case TOK.float80Literal:
        case TOK.imaginary32Literal:
        case TOK.imaginary64Literal:
        case TOK.imaginary80Literal:
        case TOK.leftParenthesis:
        case TOK.and:
        case TOK.mul:
        case TOK.min:
        case TOK.add:
        case TOK.tilde:
        case TOK.not:
        case TOK.plusPlus:
        case TOK.minusMinus:
        case TOK.sizeof_:
        case TOK._Generic:
        case TOK._assert:
        Lexp:
            auto exp = cparseExpression();
            if (token.value == TOK.identifier && exp.op == EXP.identifier)
            {
                error(token.loc, "found `%s` when expecting `;` or `=`, did you mean `%s %s = %s`?", peek(&token).toChars(), exp.toChars(), token.toChars(), peek(peek(&token)).toChars());
                nextToken();
            }
            else
                check(TOK.semicolon, "statement");
            s = new AST.ExpStatement(loc, exp);
            break;

        // type-specifiers
        case TOK.void_:
        case TOK.char_:
        case TOK.int16:
        case TOK.int32:
        case TOK.int64:
        case TOK.__int128:
        case TOK.float32:
        case TOK.float64:
        case TOK.signed:
        case TOK.unsigned:
        case TOK._Bool:
        //case TOK._Imaginary:
        case TOK._Complex:
        case TOK.struct_:
        case TOK.union_:
        case TOK.enum_:
        case TOK.typeof_:

        // storage-class-specifiers
        case TOK.typedef_:
        case TOK.extern_:
        case TOK.static_:
        case TOK._Thread_local:
        case TOK.__thread:
        case TOK.auto_:
        case TOK.register:

        // function-specifiers
        case TOK.inline:
        case TOK._Noreturn:

        // type-qualifiers
        case TOK.const_:
        case TOK.volatile:
        case TOK.restrict:
        case TOK.__stdcall:

        // alignment-specifier
        case TOK._Alignas:

        // atomic-type-specifier or type_qualifier
        case TOK._Atomic:

        case TOK.__attribute__:
        case TOK.__declspec:

        Ldeclaration:
        {
            cparseDeclaration(LVL.local);
            if (symbols.length > 1)
            {
                auto as = new AST.Statements();
                as.reserve(symbols.length);
                foreach (d; (*symbols)[])
                {
                    s = new AST.ExpStatement(loc, d);
                    as.push(s);
                }
                s = new AST.CompoundDeclarationStatement(loc, as);
                symbols.setDim(0);
            }
            else if (symbols.length == 1)
            {
                auto d = (*symbols)[0];
                s = new AST.ExpStatement(loc, d);
                symbols.setDim(0);
            }
            else
                s = new AST.ExpStatement(loc, cast(AST.Expression)null);
            if (flags & ParseStatementFlags.scope_)
                s = new AST.ScopeStatement(loc, s, token.loc);
            break;
        }

        case TOK._Static_assert:        // _Static_assert ( constant-expression, string-literal ) ;
            s = new AST.StaticAssertStatement(cparseStaticAssert());
            break;

        case TOK.leftCurly:
        {
            /* C11 6.8.2
             * compound-statement:
             *    { block-item-list (opt) }
             *
             * block-item-list:
             *    block-item
             *    block-item-list block-item
             *
             * block-item:
             *    declaration
             *    statement
             */
            nextToken();
            auto statements = new AST.Statements();
            while (token.value != TOK.rightCurly && token.value != TOK.endOfFile)
            {
                statements.push(cparseStatement(ParseStatementFlags.curlyScope));
            }
            if (endPtr)
                *endPtr = token.ptr;
            endloc = token.loc;
            if (pEndloc)
            {
                *pEndloc = token.loc;
                pEndloc = null; // don't set it again
            }
            s = new AST.CompoundStatement(loc, statements);
            if (flags & (ParseStatementFlags.scope_ | ParseStatementFlags.curlyScope))
                s = new AST.ScopeStatement(loc, s, token.loc);
            check(TOK.rightCurly, "compound statement");
            break;
        }

        case TOK.while_:
        {
            nextToken();
            check(TOK.leftParenthesis);
            auto condition = cparseExpression();
            check(TOK.rightParenthesis);
            Loc endloc;
            auto _body = cparseStatement(ParseStatementFlags.scope_, null, &endloc);
            s = new AST.WhileStatement(loc, condition, _body, endloc, null);
            break;
        }

        case TOK.semicolon:
            /* C11 6.8.3 null statement
             */
            nextToken();
            s = new AST.ExpStatement(loc, cast(AST.Expression)null);
            break;

        case TOK.do_:
        {
            nextToken();
            auto _body = cparseStatement(ParseStatementFlags.scope_);
            check(TOK.while_);
            check(TOK.leftParenthesis);
            auto condition = cparseExpression();
            check(TOK.rightParenthesis);
            check(TOK.semicolon, "terminating `;` required after do-while statement");
            s = new AST.DoStatement(loc, _body, condition, token.loc);
            break;
        }

        case TOK.for_:
        {
            AST.Statement _init;
            AST.Expression condition;
            AST.Expression increment;

            nextToken();
            check(TOK.leftParenthesis);
            if (token.value == TOK.semicolon)
            {
                _init = null;
                nextToken();
            }
            else
            {
                _init = cparseStatement(0);
            }
            if (token.value == TOK.semicolon)
            {
                condition = null;
                nextToken();
            }
            else
            {
                condition = cparseExpression();
                check(TOK.semicolon, "`for` condition");
            }
            if (token.value == TOK.rightParenthesis)
            {
                increment = null;
                nextToken();
            }
            else
            {
                increment = cparseExpression();
                check(TOK.rightParenthesis);
            }
            Loc endloc;
            auto _body = cparseStatement(ParseStatementFlags.scope_, null, &endloc);
            s = new AST.ForStatement(loc, _init, condition, increment, _body, endloc);
            break;
        }

        case TOK.if_:
        {
            nextToken();
            check(TOK.leftParenthesis);
            auto condition = cparseExpression();
            check(TOK.rightParenthesis);
            auto ifbody = cparseStatement(ParseStatementFlags.scope_);
            AST.Statement elsebody;
            if (token.value == TOK.else_)
            {
                nextToken();
                elsebody = cparseStatement(ParseStatementFlags.scope_);
            }
            else
                elsebody = null;
            if (condition && ifbody)
                s = new AST.IfStatement(loc, null, condition, ifbody, elsebody, token.loc);
            else
                s = null; // don't propagate parsing errors
            break;
        }

        case TOK.else_:
            error("found `else` without a corresponding `if` statement");
            goto Lerror;

        case TOK.switch_:
        {
            nextToken();
            check(TOK.leftParenthesis);
            auto condition = cparseExpression();
            check(TOK.rightParenthesis);
            auto _body = cparseStatement(ParseStatementFlags.scope_);
            s = new AST.SwitchStatement(loc, null, condition, _body, false, token.loc);
            break;
        }

        case TOK.case_:
        {

            nextToken();
            auto exp = cparseAssignExp();
            AST.Expression expHigh;
            if (token.value == TOK.dotDotDot)
            {
                /* Case Ranges https://gcc.gnu.org/onlinedocs/gcc/Case-Ranges.html
                 */
                nextToken();
                expHigh = cparseAssignExp();
            }
            check(TOK.colon);

            if (flags & ParseStatementFlags.curlyScope)
            {
                auto statements = new AST.Statements();
                while (token.value != TOK.case_ && token.value != TOK.default_ && token.value != TOK.endOfFile && token.value != TOK.rightCurly)
                {
                    auto cur = cparseStatement(ParseStatementFlags.curlyScope);
                    statements.push(cur);

                    // https://issues.dlang.org/show_bug.cgi?id=21739
                    // Stop at the last break s.t. the following non-case statements are
                    // not merged into the current case. This can happen for
                    // case 1: ... break;
                    // debug { case 2: ... }
                    if (cur && cur.isBreakStatement())
                        break;
                }
                s = new AST.CompoundStatement(loc, statements);
            }
            else
            {
                s = cparseStatement(0);
            }
            s = new AST.ScopeStatement(loc, s, token.loc);
            if (expHigh)
                s = new AST.CaseRangeStatement(loc, exp, expHigh, s);
            else
                s = new AST.CaseStatement(loc, exp, s);
            break;
        }

        case TOK.default_:
        {
            nextToken();
            check(TOK.colon);

            if (flags & ParseStatementFlags.curlyScope)
            {
                auto statements = new AST.Statements();
                while (token.value != TOK.case_ && token.value != TOK.default_ && token.value != TOK.endOfFile && token.value != TOK.rightCurly)
                {
                    statements.push(cparseStatement(ParseStatementFlags.curlyScope));
                }
                s = new AST.CompoundStatement(loc, statements);
            }
            else
                s = cparseStatement(0);
            s = new AST.ScopeStatement(loc, s, token.loc);
            s = new AST.DefaultStatement(loc, s);
            break;
        }

        case TOK.return_:
        {
            /*  return ;
             *  return expression ;
             */
            nextToken();
            auto exp = token.value == TOK.semicolon ? null : cparseExpression();
            check(TOK.semicolon, "`return` statement");
            s = new AST.ReturnStatement(loc, exp);
            break;
        }

        case TOK.break_:
            nextToken();
            check(TOK.semicolon, "`break` statement");
            s = new AST.BreakStatement(loc, null);
            break;

        case TOK.continue_:
            nextToken();
            check(TOK.semicolon, "`continue` statement");
            s = new AST.ContinueStatement(loc, null);
            break;

        case TOK.goto_:
        {
            Identifier ident;
            nextToken();
            if (token.value == TOK.identifier)
            {
                ident = token.ident;
                nextToken();
            }
            else if (token.value == TOK.mul)
            {
                /* https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
                 */
                error("`goto *` computed goto extension is not supported");
                ident = null;
                cparseUnaryExp();   // parse and throw away
            }
            else
            {
                error("identifier expected following `goto`");
                ident = null;
            }
            s = new AST.GotoStatement(loc, ident);
            check(TOK.semicolon, "`goto` statement");
            break;
        }

        case TOK.asm_:
            switch (peekNext())
            {
                case TOK.goto_:
                case TOK.inline:
                case TOK.volatile:
                case TOK.leftParenthesis:
                    s = cparseGnuAsm();
                    break;

                default:
                    // ImportC extensions: parse as a D asm block.
                    s = parseAsm(compileEnv.masm);
                    break;
            }
            break;

        default:
            error("found `%s` instead of statement", token.toChars());
            goto Lerror;

        Lerror:
            panic();
            if (token.value == TOK.semicolon)
                nextToken();
            s = null;
            break;
        }
        if (pEndloc)
            *pEndloc = prevloc;
        symbols = symbolsSave;
        typedefTab.setDim(typedefTabLengthSave);
        return s;
    }

    //}
    /*******************************************************************************/
    /********************************* Expression Parser ***************************/
    //{

    /**************
     * C11 6.5.17
     * expression:
     *  assignment-expression
     *  expression , assignment-expression
     */
    AST.Expression cparseExpression()
    {
        auto loc = token.loc;

        //printf("cparseExpression() loc = %d\n", loc.linnum);
        auto e = cparseAssignExp();
        while (token.value == TOK.comma)
        {
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.CommaExp(loc, e, e2, false);
            loc = token.loc;
        }
        return e;
    }


    /*********************
     * C11 6.5.1
     * primary-expression:
     *    identifier
     *    constant
     *    string-literal
     *    ( expression )
     *    generic-selection
     *    __builtin_va_arg(assign_expression, type)
     */
    AST.Expression cparsePrimaryExp()
    {
        AST.Expression e;
        const loc = token.loc;

        //printf("parsePrimaryExp(): loc = %d\n", loc.linnum);
        switch (token.value)
        {
        case TOK.identifier:
            const id = token.ident.toString();
            if (id.length > 2 && id[0] == '_' && id[1] == '_')  // leading double underscore
            {
                if (token.ident is Id.__func__)
                {
                    addFuncName = true;     // implicitly declare __func__
                }
                else if (token.ident is Id.builtin_va_arg)
                {
                    e = cparseBuiltin_va_arg();
                    break;
                }
                else
                    importBuiltins = true;  // probably one of those compiler extensions
            }
            e = new AST.IdentifierExp(loc, token.ident);
            nextToken();
            break;

        case TOK.charLiteral:
        case TOK.int32Literal:
            e = new AST.IntegerExp(loc, token.intvalue, AST.Type.tint32);
            nextToken();
            break;

        case TOK.wcharLiteral:
            e = new AST.IntegerExp(loc, token.intvalue, AST.Type.tuns16);
            nextToken();
            break;

        case TOK.dcharLiteral:
        case TOK.uns32Literal:
            e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tuns32);
            nextToken();
            break;

        case TOK.int64Literal:
            e = new AST.IntegerExp(loc, token.intvalue, AST.Type.tint64);
            nextToken();
            break;

        case TOK.uns64Literal:
            e = new AST.IntegerExp(loc, token.unsvalue, AST.Type.tuns64);
            nextToken();
            break;

        case TOK.float32Literal:
            e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat32);
            nextToken();
            break;

        case TOK.float64Literal:
            e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat64);
            nextToken();
            break;

        case TOK.float80Literal:
            e = new AST.RealExp(loc, token.floatvalue, AST.Type.tfloat80);
            nextToken();
            break;

        case TOK.imaginary32Literal:
            e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary32);
            nextToken();
            break;

        case TOK.imaginary64Literal:
            e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary64);
            nextToken();
            break;

        case TOK.imaginary80Literal:
            e = new AST.RealExp(loc, token.floatvalue, AST.Type.timaginary80);
            nextToken();
            break;

        case TOK.string_:
        {
            // cat adjacent strings
            auto s = token.ustring;
            auto len = token.len;
            auto postfix = token.postfix;
            while (1)
            {
                nextToken();
                if (token.value == TOK.string_)
                {
                    if (token.postfix)
                    {
                        if (token.postfix != postfix)
                            error(token.loc, "mismatched string literal postfixes `'%c'` and `'%c'`", postfix, token.postfix);
                        postfix = token.postfix;
                    }

                    const len1 = len;
                    const len2 = token.len;
                    len = len1 + len2;
                    auto s2 = cast(char*)mem.xmalloc_noscan(len * char.sizeof);
                    memcpy(s2, s, len1 * char.sizeof);
                    memcpy(s2 + len1, token.ustring, len2 * char.sizeof);
                    s = s2;
                }
                else
                    break;
            }
            e = new AST.StringExp(loc, s[0 .. len], len, 1, postfix);
            break;
        }

        case TOK.leftParenthesis:
            nextToken();
            if (token.value == TOK.leftCurly)
                e = cparseStatementExpression();        // gcc extension
            else
                e = cparseExpression();
            check(TOK.rightParenthesis);
            break;

        case TOK._Generic:
            e = cparseGenericSelection();
            break;

        case TOK._assert:  // __check(assign-exp) extension
            nextToken();
            check(TOK.leftParenthesis, "`__check`");
            e = parseAssignExp();
            check(TOK.rightParenthesis);
            e = new AST.AssertExp(loc, e, null);
            break;

        default:
            error("expression expected, not `%s`", token.toChars());
            // Anything for e, as long as it's not NULL
            e = new AST.IntegerExp(loc, 0, AST.Type.tint32);
            nextToken();
            break;
        }
        return e;
    }

    /*********************************
     * C11 6.5.2
     * postfix-expression:
     *    primary-expression
     *    postfix-expression [ expression ]
     *    postfix-expression ( argument-expression-list (opt) )
     *    postfix-expression . identifier
     *    postfix-expression -> identifier
     *    postfix-expression ++
     *    postfix-expression --
     *    ( type-name ) { initializer-list }
     *    ( type-name ) { initializer-list , }
     *
     * argument-expression-list:
     *    assignment-expression
     *    argument-expression-list , assignment-expression
     */
    private AST.Expression cparsePostfixExp(AST.Expression e)
    {
        e = cparsePrimaryExp();
        return cparsePostfixOperators(e);
    }

    /********************************
     * C11 6.5.2
     * Parse a series of operators for a postfix expression after already parsing
     * a primary-expression or compound literal expression.
     * Params:
     *      e = parsed primary or compound literal expression
     * Returns:
     *      parsed postfix expression
     */
    private AST.Expression cparsePostfixOperators(AST.Expression e)
    {
        while (1)
        {
            const loc = token.loc;
            switch (token.value)
            {
            case TOK.dot:
                nextToken();
                if (token.value == TOK.identifier)
                {
                    Identifier id = token.ident;
                    e = new AST.DotIdExp(loc, e, id);
                    break;
                }
                error("identifier expected following `.`, not `%s`", token.toChars());
                break;

            case TOK.arrow:
                nextToken();
                if (token.value == TOK.identifier)
                {
                    Identifier id = token.ident;
                    auto die = new AST.DotIdExp(loc, e, id);
                    die.arrow = true;
                    e = die;
                    break;
                }
                error("identifier expected following `->`, not `%s`", token.toChars());
                break;

            case TOK.plusPlus:
                e = new AST.PostExp(EXP.plusPlus, loc, e);
                break;

            case TOK.minusMinus:
                e = new AST.PostExp(EXP.minusMinus, loc, e);
                break;

            case TOK.leftParenthesis:
                e = new AST.CallExp(loc, e, cparseArguments());
                continue;

            case TOK.leftBracket:
                {
                    // array dereferences:
                    //      array[index]
                    AST.Expression index;
                    auto arguments = new AST.Expressions();

                    inBrackets++;
                    nextToken();
                    index = cparseAssignExp();
                    arguments.push(index);
                    check(TOK.rightBracket);
                    inBrackets--;
                    e = new AST.ArrayExp(loc, e, arguments);
                    continue;
                }
            default:
                return e;
            }
            nextToken();
        }
    }

    /************************
     * C11 6.5.3
     * unary-expression:
     *    postfix-expression
     *    ++ unary-expression
     *    -- unary-expression
     *    unary-operator cast-expression
     *    sizeof unary-expression
     *    sizeof ( type-name )
     *    _Alignof ( type-name )
     *
     * unary-operator:
     *    & * + - ~ !
     */
    private AST.Expression cparseUnaryExp()
    {
        AST.Expression e;
        const loc = token.loc;

        switch (token.value)
        {
        case TOK.plusPlus:
            nextToken();
            // Parse `++` as an unary operator so that cast expressions only give
            // an error for being non-lvalues.
            e = cparseCastExp();
            e = new AST.PreExp(EXP.prePlusPlus, loc, e);
            break;

        case TOK.minusMinus:
            nextToken();
            // Parse `--` as an unary operator, same as prefix increment.
            e = cparseCastExp();
            e = new AST.PreExp(EXP.preMinusMinus, loc, e);
            break;

        case TOK.and:
            nextToken();
            e = cparseCastExp();
            e = new AST.AddrExp(loc, e);
            break;

        case TOK.mul:
            nextToken();
            e = cparseCastExp();
            e = new AST.PtrExp(loc, e);
            break;

        case TOK.min:
            nextToken();
            e = cparseCastExp();
            e = new AST.NegExp(loc, e);
            break;

        case TOK.add:
            nextToken();
            e = cparseCastExp();
            e = new AST.UAddExp(loc, e);
            break;

        case TOK.not:
            nextToken();
            e = cparseCastExp();
            e = new AST.NotExp(loc, e);
            break;

        case TOK.tilde:
            nextToken();
            e = cparseCastExp();
            e = new AST.ComExp(loc, e);
            break;

        case TOK.sizeof_:
        {
            nextToken();
            if (token.value == TOK.leftParenthesis)
            {
                auto tk = peek(&token);
                if (isTypeName(tk))
                {
                    /* Expression may be either be requesting the sizeof a type-name
                     * or a compound literal, which requires checking whether
                     * the next token is leftCurly
                     */
                    nextToken();
                    auto t = cparseTypeName();
                    check(TOK.rightParenthesis);
                    if (token.value == TOK.leftCurly)
                    {
                        // ( type-name ) { initializer-list }
                        auto ci = cparseInitializer();
                        e = new AST.CompoundLiteralExp(loc, t, ci);
                        e = cparsePostfixOperators(e);
                    }
                    else
                    {
                        // ( type-name )
                        e = new AST.TypeExp(loc, t);
                    }
                }
                else
                {
                    // must be an expression
                    e = cparseUnaryExp();
                }
            }
            else
            {
                //C11 6.5.3
                e = cparseUnaryExp();
            }

            e = new AST.DotIdExp(loc, e, Id.__sizeof);
            break;
        }

        case TOK.andAnd:
            /* https://gcc.gnu.org/onlinedocs/gcc/Labels-as-Values.html
             */
            error("unary `&&` computed goto extension is not supported");
            nextToken();
            e = cparseCastExp();
            break;

        case TOK._Alignof:
        {
            nextToken();
            check(TOK.leftParenthesis);
            auto t = cparseTypeName();
            check(TOK.rightParenthesis);
            e = new AST.TypeExp(loc, t);
            e = new AST.DotIdExp(loc, e, Id.__xalignof);
            break;
        }

        default:
            e = cparsePostfixExp(e);
            break;
        }
        assert(e);
        return e;
    }

    /**************
     * C11 6.5.4
     * cast-expression
     *    unary-expression
     *    ( type-name ) cast-expression
     */
    private AST.Expression cparseCastExp()
    {
        if (token.value != TOK.leftParenthesis)
            return cparseUnaryExp();

        //printf("cparseCastExp()\n");
        auto tk = peek(&token);
        bool iscast;
        bool isexp;
        if (tk.value == TOK.identifier)
        {
            iscast = isTypedef(tk.ident);
            isexp = !iscast;
        }
        if (isexp)
        {
            // ( identifier ) is an expression
            return cparseUnaryExp();
        }

        // If ( type-name )
        auto pt = &token;

        if (!isCastExpression(pt))
            return cparseUnaryExp();

        // Expression may be either a cast or a compound literal, which
        // requires checking whether the next token is leftCurly
        const loc = token.loc;
        nextToken();
        auto t = cparseTypeName();
        check(TOK.rightParenthesis);
        pt = &token;

        if (token.value == TOK.leftCurly)
        {
            // C11 6.5.2.5 ( type-name ) { initializer-list }
            auto ci = cparseInitializer();
            auto ce = new AST.CompoundLiteralExp(loc, t, ci);
            return cparsePostfixOperators(ce);
        }

        if (iscast)
        {
            // ( type-name ) cast-expression
            auto ce = cparseCastExp();
            return new AST.CastExp(loc, ce, t);
        }

        if (t.isTypeIdentifier() &&
            isexp &&
            token.value == TOK.leftParenthesis &&
            !isCastExpression(pt))
        {
            /* (t)(...)... might be a cast expression or a function call,
             * with different grammars: a cast would be cparseCastExp(),
             * a function call would be cparsePostfixExp(CallExp(cparseArguments())).
             * We can't know until t is known. So, parse it as a function call
             * and let semantic() rewrite the AST as a CastExp if it turns out
             * to be a type.
             */
            auto ie = new AST.IdentifierExp(loc, t.isTypeIdentifier().ident);
            ie.parens = true;    // let semantic know it might be a CastExp
            AST.Expression e = new AST.CallExp(loc, ie, cparseArguments());
            return cparsePostfixOperators(e);
        }

        // ( type-name ) cast-expression
        auto ce = cparseCastExp();
        return new AST.CastExp(loc, ce, t);
    }

    /**************
     * C11 6.5.5
     * multiplicative-expression
     *    cast-expression
     *    multiplicative-expression * cast-expression
     *    multiplicative-expression / cast-expression
     *    multiplicative-expression % cast-expression
     */
    private AST.Expression cparseMulExp()
    {
        const loc = token.loc;
        auto e = cparseCastExp();

        while (1)
        {
            switch (token.value)
            {
            case TOK.mul:
                nextToken();
                auto e2 = cparseCastExp();
                e = new AST.MulExp(loc, e, e2);
                continue;

            case TOK.div:
                nextToken();
                auto e2 = cparseCastExp();
                e = new AST.DivExp(loc, e, e2);
                continue;

            case TOK.mod:
                nextToken();
                auto e2 = cparseCastExp();
                e = new AST.ModExp(loc, e, e2);
                continue;

            default:
                break;
            }
            break;
        }
        return e;
    }

    /**************
     * C11 6.5.6
     * additive-expression
     *    multiplicative-expression
     *    additive-expression + multiplicative-expression
     *    additive-expression - multiplicative-expression
     */
    private AST.Expression cparseAddExp()
    {
        const loc = token.loc;
        auto e = cparseMulExp();

        while (1)
        {
            switch (token.value)
            {
            case TOK.add:
                nextToken();
                auto e2 = cparseMulExp();
                e = new AST.AddExp(loc, e, e2);
                continue;

            case TOK.min:
                nextToken();
                auto e2 = cparseMulExp();
                e = new AST.MinExp(loc, e, e2);
                continue;

            default:
                break;
            }
            break;
        }
        return e;
    }

    /**************
     * C11 6.5.7
     * shift-expression
     *    additive-expression
     *    shift-expression << additive-expression
     *    shift-expression >> additive-expression
     */
    private AST.Expression cparseShiftExp()
    {
        const loc = token.loc;
        auto e = cparseAddExp();

        while (1)
        {
            switch (token.value)
            {
            case TOK.leftShift:
                nextToken();
                auto e2 = cparseAddExp();
                e = new AST.ShlExp(loc, e, e2);
                continue;

            case TOK.rightShift:
                nextToken();
                auto e2 = cparseAddExp();
                e = new AST.ShrExp(loc, e, e2);
                continue;

            default:
                break;
            }
            break;
        }
        return e;
    }

    /**************
     * C11 6.5.8
     * relational-expression
     *    shift-expression
     *    relational-expression < shift-expression
     *    relational-expression > shift-expression
     *    relational-expression <= shift-expression
     *    relational-expression >= shift-expression
     */
    private AST.Expression cparseRelationalExp()
    {
        const loc = token.loc;

        auto e = cparseShiftExp();

        EXP op = EXP.reserved;
        switch (token.value)
        {
        case TOK.lessThan:       op = EXP.lessThan; goto Lcmp;
        case TOK.lessOrEqual:    op = EXP.lessOrEqual; goto Lcmp;
        case TOK.greaterThan:    op = EXP.greaterThan; goto Lcmp;
        case TOK.greaterOrEqual: op = EXP.greaterOrEqual; goto Lcmp;
        Lcmp:
            nextToken();
            auto e2 = cparseShiftExp();
            e = new AST.CmpExp(op, loc, e, e2);
            break;

        default:
            break;
        }
        return e;
    }

    /**************
     * C11 6.5.9
     * equality-expression
     *    relational-expression
     *    equality-expression == relational-expression
     *    equality-expression != relational-expression
     */
    private AST.Expression cparseEqualityExp()
    {
        const loc = token.loc;

        auto e = cparseRelationalExp();

        EXP op = EXP.reserved;
        switch (token.value)
        {
        case TOK.equal:         op = EXP.equal;    goto Lequal;
        case TOK.notEqual:      op = EXP.notEqual; goto Lequal;
        Lequal:
            nextToken();
            auto e2 = cparseRelationalExp();
            e = new AST.EqualExp(op, loc, e, e2);
            break;

        default:
            break;
        }
        return e;
    }

    /**************
     * C11 6.5.10
     * AND-expression
     *    equality-expression
     *    AND-expression & equality-expression
     */
    private AST.Expression cparseAndExp()
    {
        Loc loc = token.loc;
        auto e = cparseEqualityExp();
        while (token.value == TOK.and)
        {
            nextToken();
            auto e2 = cparseEqualityExp();
            e = new AST.AndExp(loc, e, e2);
            loc = token.loc;
        }
        return e;
    }

    /**************
     * C11 6.5.11
     * exclusive-OR-expression
     *    AND-expression
     *    exclusive-OR-expression ^ AND-expression
     */
    private AST.Expression cparseXorExp()
    {
        const loc = token.loc;

        auto e = cparseAndExp();
        while (token.value == TOK.xor)
        {
            nextToken();
            auto e2 = cparseAndExp();
            e = new AST.XorExp(loc, e, e2);
        }
        return e;
    }

    /**************
     * C11 6.5.12
     * inclusive-OR-expression
     *    exclusive-OR-expression
     *    inclusive-OR-expression | exclusive-OR-expression
     */
    private AST.Expression cparseOrExp()
    {
        const loc = token.loc;

        auto e = cparseXorExp();
        while (token.value == TOK.or)
        {
            nextToken();
            auto e2 = cparseXorExp();
            e = new AST.OrExp(loc, e, e2);
        }
        return e;
    }

    /**************
     * C11 6.5.13
     * logical-AND-expression
     *    inclusive-OR-expression
     *    logical-AND-expression && inclusive-OR-expression
     */
    private AST.Expression cparseAndAndExp()
    {
        const loc = token.loc;

        auto e = cparseOrExp();
        while (token.value == TOK.andAnd)
        {
            nextToken();
            auto e2 = cparseOrExp();
            e = new AST.LogicalExp(loc, EXP.andAnd, e, e2);
        }
        return e;
    }

    /**************
     * C11 6.5.14
     * logical-OR-expression
     *    logical-AND-expression
     *    logical-OR-expression || logical-AND-expression
     */
    private AST.Expression cparseOrOrExp()
    {
        const loc = token.loc;

        auto e = cparseAndAndExp();
        while (token.value == TOK.orOr)
        {
            nextToken();
            auto e2 = cparseAndAndExp();
            e = new AST.LogicalExp(loc, EXP.orOr, e, e2);
        }
        return e;
    }

    /**************
     * C11 6.5.15
     * conditional-expression:
     *    logical-OR-expression
     *    logical-OR-expression ? expression : conditional-expression
     */
    private AST.Expression cparseCondExp()
    {
        const loc = token.loc;

        auto e = cparseOrOrExp();
        if (token.value == TOK.question)
        {
            nextToken();
            auto e1 = cparseExpression();
            check(TOK.colon);
            auto e2 = cparseCondExp();
            e = new AST.CondExp(loc, e, e1, e2);
        }
        return e;
    }

    /**************
     * C11 6.5.16
     * assignment-expression:
     *    conditional-expression
     *    unary-expression assignment-operator assignment-expression
     *
     * assignment-operator:
     *    = *= /= %= += -= <<= >>= &= ^= |=
     */
    AST.Expression cparseAssignExp()
    {
        AST.Expression e;
        e = cparseCondExp(); // constrain it to being unary-expression in semantic pass
        if (e is null)
            return e;

        const loc = token.loc;
        switch (token.value)
        {
        case TOK.assign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.AssignExp(loc, e, e2);
            break;

        case TOK.addAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.AddAssignExp(loc, e, e2);
            break;

        case TOK.minAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.MinAssignExp(loc, e, e2);
            break;

        case TOK.mulAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.MulAssignExp(loc, e, e2);
            break;

        case TOK.divAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.DivAssignExp(loc, e, e2);
            break;

        case TOK.modAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.ModAssignExp(loc, e, e2);
            break;

        case TOK.andAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.AndAssignExp(loc, e, e2);
            break;

        case TOK.orAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.OrAssignExp(loc, e, e2);
            break;

        case TOK.xorAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.XorAssignExp(loc, e, e2);
            break;

        case TOK.leftShiftAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.ShlAssignExp(loc, e, e2);
            break;

        case TOK.rightShiftAssign:
            nextToken();
            auto e2 = cparseAssignExp();
            e = new AST.ShrAssignExp(loc, e, e2);
            break;

        default:
            break;
        }

        return e;
    }

    /***********************
     * C11 6.5.1.1
     * _Generic ( assignment-expression, generic-assoc-list )
     *
     * generic-assoc-list:
     *   generic-association
     *   generic-assoc-list generic-association
     *
     * generic-association:
     *   type-name : assignment-expression
     *   default : assignment-expression
     */
    private AST.Expression cparseGenericSelection()
    {
        const loc = token.loc;
        nextToken();
        check(TOK.leftParenthesis);
        auto cntlExp = cparseAssignExp();
        check(TOK.comma);
        auto types = new AST.Types();
        auto exps = new AST.Expressions();
        bool sawDefault;
        while (1)
        {
            AST.Type t;
            if (token.value == TOK.default_)
            {
                nextToken();
                if (sawDefault)
                    error("only one `default` allowed in generic-assoc-list");
                sawDefault = true;
                t = null;
            }
            else
                t = cparseTypeName();
            types.push(t);

            check(TOK.colon);
            auto e = cparseAssignExp();
            exps.push(e);
            if (token.value == TOK.rightParenthesis || token.value == TOK.endOfFile)
                break;
            check(TOK.comma);
        }
        check(TOK.rightParenthesis);
        return new AST.GenericExp(loc, cntlExp, types, exps);
    }

    /***********************
     * C11 6.6 Constant expressions
     * constant-expression:
     *   conditional-expression
     */
    private AST.Expression cparseConstantExp()
    {
        return cparseAssignExp();
    }

    /*****************************
     * gcc extension:
     *    type __builtin_va_arg(assign-expression, type)
     * Rewrite as `va_arg` template from `core.stdc.stdarg`:
     *    va_arg!(type)(assign-expression);
     * Lexer is on `__builtin_va_arg`
     */
    private AST.Expression cparseBuiltin_va_arg()
    {
        importBuiltins = true;  // need core.stdc.stdarg

        nextToken();
        check(TOK.leftParenthesis);

        auto arguments = new AST.Expressions();
        auto arg = cparseAssignExp();
        arguments.push(arg);

        check(TOK.comma);

        auto t = cparseTypeName();
        auto tiargs = new AST.Objects();
        tiargs.push(t);

        const loc = loc;
        auto ti = new AST.TemplateInstance(loc, Id.va_arg, tiargs);
        auto tie = new AST.ScopeExp(loc, ti);

        AST.Expression e = new AST.CallExp(loc, tie, arguments);

        check(TOK.rightParenthesis);
        return e;
    }

    /*****************************
     * gcc extension: https://gcc.gnu.org/onlinedocs/gcc/Statement-Exprs.html
     * Represent as a function literal, then call the function literal.
     * Parser is on opening curly brace.
     */
    private AST.Expression cparseStatementExpression()
    {
        AST.ParameterList parameterList;
        StorageClass stc = 0;
        const loc = token.loc;
        auto symbolsSave = symbols;
        symbols = new AST.Dsymbols();
        typedefTab.push(null);
        auto fbody = cparseStatement(ParseStatementFlags.scope_);
        typedefTab.pop();                                        // end of function scope
        symbols = symbolsSave;

        // Rewrite last ExpStatement (if there is one) as a ReturnStatement
        auto ss = fbody.isScopeStatement();
        auto cs = ss.statement.isCompoundStatement();
        assert(cs);
        if (const len = (*cs.statements).length)
        {
            auto s = (*cs.statements)[len - 1];
            if (s)   // error recovery should be with ErrorStatement, not null
            {
                if (auto es = s.isExpStatement())
                    (*cs.statements)[len - 1] = new AST.ReturnStatement(es.loc, es.exp);
            }
        }

        auto tf = new AST.TypeFunction(parameterList, null, LINK.d, stc);
        auto fd = new AST.FuncLiteralDeclaration(loc, token.loc, tf, TOK.delegate_, null, null, 0);
        fd.fbody = fbody;

        auto fe = new AST.FuncExp(loc, fd);
        auto args = new AST.Expressions();
        auto e = new AST.CallExp(loc, fe, args);   // call the function literal
        return e;
    }

    //}
    /********************************************************************************/
    /********************************* Declaration Parser ***************************/
    //{

    /*************************************
     * C11 6.7
     * declaration:
     *    declaration-specifiers init-declarator-list (opt) ;
     *    static_assert-declaration
     *
     * init-declarator-list:
     *    init-declarator
     *    init-declarator-list , init-declarator
     *
     * init-declarator:
     *    declarator
     *    declarator = initializer
     *
     * Params:
     *    level = declaration context
     */
    void cparseDeclaration(LVL level)
    {
        //printf("cparseDeclaration(level = %d)\n", level);
        if (token.value == TOK._Static_assert)
        {
            auto s = cparseStaticAssert();
            symbols.push(s);
            return;
        }

        if (token.value == TOK.__pragma)
        {
            uupragmaDirective(scanloc);
            return;
        }

        if (token.value == TOK._import) // import declaration extension
        {
            auto a = parseImport();
            if (a && a.length)
                symbols.append(a);
            return;
        }

        const typedefTabLengthSave = typedefTab.length;
        auto symbolsSave = symbols;
        Specifier specifier;
        specifier.packalign = this.packalign;
        auto tspec = cparseDeclarationSpecifiers(level, specifier);

        AST.Dsymbol declareTag(AST.TypeTag tt, ref Specifier specifier)
        {
            /* `struct tag;` and `struct tag { ... };`
             * always result in a declaration in the current scope
             */
            auto stag = (tt.tok == TOK.struct_) ? new AST.StructDeclaration(tt.loc, tt.id, false) :
                        (tt.tok == TOK.union_)  ? new AST.UnionDeclaration(tt.loc, tt.id) :
                                                  new AST.EnumDeclaration(tt.loc, tt.id, tt.base);
            if (!tt.packalign.isUnknown())
            {
                // saw `struct __declspec(align(N)) Tag ...`
                auto st = stag.isStructDeclaration();
                st.alignment = tt.packalign;
            }
            stag.members = tt.members;
            tt.members = null;
            if (!symbols)
                symbols = new AST.Dsymbols();
            auto stags = applySpecifier(stag, specifier);
            symbols.push(stags);
            return stags;
        }

        /* If a declarator does not follow, it is unnamed
         */
        if (token.value == TOK.semicolon)
        {
            if (!tspec)
            {
                nextToken();
                return;         // accept empty declaration as an extension
            }

            if (auto ti = tspec.isTypeIdentifier())
            {
                // C11 6.7.2-2
                error("type-specifier missing for declaration of `%s`", ti.ident.toChars());
                nextToken();
                return;
            }

            nextToken();
            auto tt = tspec.isTypeTag();
            if (!tt ||
                !tt.id && (tt.tok == TOK.struct_ || tt.tok == TOK.union_))
                return; // legal but meaningless empty declaration, ignore it

            auto stags = declareTag(tt, specifier);

            if (0 && tt.tok == TOK.enum_)    // C11 proscribes enums with no members, but we allow it
            {
                if (!tt.members)
                    error(tt.loc, "`enum %s` has no members", stags.toChars());
            }
            return;
        }

        if (!tspec)
        {
            error("no type for declarator before `%s`", token.toChars());
            panic();
            nextToken();
            return;
        }

        if (tspec && specifier.mod & MOD.xconst)
        {
            tspec = toConst(tspec);
            specifier.mod &= ~MOD.xnone;          // 'used' it
        }

        void scanPastSemicolon()
        {
            while (token.value != TOK.semicolon && token.value != TOK.endOfFile)
                nextToken();
            nextToken();
        }

        if (token.value == TOK.assign && tspec && tspec.isTypeIdentifier())
        {
            /* C11 6.7.2-2
             * Special check for `const b = 1;` because some compilers allow it
             */
            error("type-specifier omitted for declaration of `%s`", tspec.isTypeIdentifier().ident.toChars());
            return scanPastSemicolon();
        }

        bool first = true;
        while (1)
        {
            Identifier id;
            AST.StringExp asmName;
            auto dt = cparseDeclarator(DTR.xdirect, tspec, id, specifier);
            if (!dt)
            {
                panic();
                nextToken();
                break;          // error recovery
            }

            /* GNU Extensions
             * init-declarator:
             *    declarator simple-asm-expr (opt) gnu-attributes (opt)
             *    declarator simple-asm-expr (opt) gnu-attributes (opt) = initializer
             *
             * Clang also allows simple-asm-expr after gnu-attributes.
             */
            while (1)
            {
                if (token.value == TOK.asm_)
                {
                    asmName = cparseGnuAsmLabel();
                    /* This is a data definition, there cannot now be a
                     * function definition.
                     */
                    first = false;
                }
                else if (token.value == TOK.__attribute__)
                    cparseGnuAttributes(specifier);
                else
                    break;
            }

            switch (token.value)
            {
                case TOK.assign:
                case TOK.comma:
                case TOK.semicolon:
                    /* This is a data definition, there cannot now be a
                     * function definition.
                     */
                    first = false;
                    break;

                default:
                    break;
            }

            if (specifier.alignExps && dt.isTypeFunction())
                error("no alignment-specifier for function declaration"); // C11 6.7.5-2
            if (specifier.alignExps && specifier.scw == SCW.xregister)
                error("no alignment-specifier for `register` storage class"); // C11 6.7.5-2

            /* C11 6.9.1 Function Definitions
             * function-definition:
             *   declaration-specifiers declarator declaration-list (opt) compound-statement
             *
             * declaration-list:
             *    declaration
             *    declaration-list declaration
             */
            auto t = &token;
            if (first &&                   // first declarator
                id &&
                dt.isTypeFunction() &&     // function type not inherited from a typedef
                isDeclarationList(t) &&    // optional declaration-list
                level == LVL.global &&     // function definitions only at global scope
                t.value == TOK.leftCurly)  // start of compound-statement
            {
                auto s = cparseFunctionDefinition(id, dt.isTypeFunction(), specifier);
                typedefTab.setDim(typedefTabLengthSave);
                symbols = symbolsSave;
                if (specifier.mod & MOD.x__stdcall)
                {
                    // If this function is __stdcall, wrap it in a LinkDeclaration so that
                    // it's extern(Windows) when imported in D.
                    auto decls = new AST.Dsymbols(1);
                    (*decls)[0] = s;
                    s = new AST.LinkDeclaration(s.loc, LINK.windows, decls);
                }
                symbols.push(s);
                return;
            }
            AST.Dsymbol s = null;
            typedefTab.setDim(typedefTabLengthSave);
            symbols = symbolsSave;
            if (!symbols)
                symbols = new AST.Dsymbols;     // lazilly create it

            if (level != LVL.global && !tspec && !specifier.scw && !specifier.mod)
                error("declaration-specifier-seq required");
            else if (specifier.scw == SCW.xtypedef)
            {
                if (token.value == TOK.assign)
                    error("no initializer for typedef declaration");
                if (specifier.alignExps)
                    error("no alignment-specifier for typedef declaration"); // C11 6.7.5-2

                if (specifier.vector_size)
                {
                    auto length = new AST.IntegerExp(token.loc, specifier.vector_size / dt.size(), AST.Type.tuns32);
                    auto tsa = new AST.TypeSArray(dt, length);
                    dt = new AST.TypeVector(tsa);
                    specifier.vector_size = 0;          // used it up
                }

                bool isalias = true;
                Identifier idt;
                if (auto ts = dt.isTypeStruct())
                {
                    if (ts.sym.isAnonymous())
                    {
                        // This is a typedef for an anonymous struct-or-union.
                        // Directly set the ident for the struct-or-union.
                        ts.sym.ident = id;
                        isalias = false;
                    }
                    idt = ts.sym.ident;
                }
                else if (auto te = dt.isTypeEnum())
                {
                    if (te.sym.isAnonymous())
                    {
                        // This is a typedef for an anonymous enum.
                        te.sym.ident = id;
                        isalias = false;
                    }
                    idt = te.sym.ident;
                }
                else if (auto tt = dt.isTypeTag())
                {
                    if (!tt.id && id)
                        /* This applies for enums declared as
                         * typedef enum {A} E;
                         */
                        tt.id = id;
                    Specifier spec;
                    declareTag(tt, spec);
                    idt = tt.id;
                }
                if (isalias)
                {
                    //printf("AliasDeclaration %s %s\n", id.toChars(), dt.toChars());
                    auto ad = new AST.AliasDeclaration(token.loc, id, dt);
                    if (id == idt)
                        ad.adFlags |= ad.hidden; // do not print when generating .di files
                    s = ad;
                }

                insertTypedefToTypedefTab(id, dt);       // remember typedefs
            }
            else if (id)
            {
                if (auto tt = dt.isTypeTag())
                {
                    if (tt.members && (tt.id || tt.tok == TOK.enum_))
                    {
                        Specifier spec;
                        declareTag(tt, spec);
                    }
                }

                if (level == LVL.prototype)
                    break;      // declared later as Parameter, not VarDeclaration

                if (dt.ty == AST.Tvoid)
                    error("`void` has no value");

                AST.Initializer initializer;
                bool hasInitializer;
                if (token.value == TOK.assign)
                {
                    nextToken();
                    hasInitializer = true;
                    initializer = cparseInitializer();
                }
                // declare the symbol
                assert(id);

                if (isFunctionTypedef(dt))
                {
                    if (hasInitializer)
                        error("no initializer for function declaration");
                    if (specifier.scw & SCW.x_Thread_local)
                        error("functions cannot be `_Thread_local`"); // C11 6.7.1-4
                    StorageClass stc = specifiersToSTC(level, specifier);
                    stc &= ~STC.gshared;        // no gshared functions
                    auto fd = new AST.FuncDeclaration(token.loc, Loc.initial, id, stc, dt, specifier.noreturn);
                    specifiersToFuncDeclaration(fd, specifier);
                    s = fd;
                }
                else
                {
                    // Give non-extern variables an implicit void initializer
                    // if one has not been explicitly set.
                    if (!hasInitializer &&
                        !(specifier.scw & (SCW.xextern | SCW.xstatic | SCW.x_Thread_local) || level == LVL.global))
                        initializer = new AST.VoidInitializer(token.loc);
                    auto vd = new AST.VarDeclaration(token.loc, dt, id, initializer, specifiersToSTC(level, specifier));
                    specifiersToVarDeclaration(vd, specifier);
                    s = vd;
                }
                if (level != LVL.global)
                    insertIdToTypedefTab(id);   // non-typedef declarations can hide typedefs in outer scopes
            }
            if (s !is null)
            {
                // Saw `asm("name")` in the function, type, or variable definition.
                // This is equivalent to `pragma(mangle, "name")` in D
                if (asmName)
                {
                    /*
                    https://issues.dlang.org/show_bug.cgi?id=23012
                    Ideally this would be translated to a pragma(mangle)
                    decl. This is not possible because ImportC symbols are
                    (currently) merged before semantic analysis is performed,
                    so the pragma(mangle) never effects any change on the declarations
                    it pertains too.

                    Writing to mangleOverride directly avoids this, and is possible
                    because C only a StringExp is allowed unlike a full fat pragma(mangle)
                    which is more liberal.
                    */
                    if (auto p = s.isDeclaration())
                    {
                        auto str = asmName.peekString();
                        p.mangleOverride = str;
//                      p.adFlags |= AST.VarDeclaration.nounderscore;
                        p.adFlags |= 4; // cannot get above line to compile on Ubuntu
                    }
                }
                s = applySpecifier(s, specifier);
                if (level == LVL.local || (specifier.mod & MOD.x__stdcall))
                {
                    // Wrap the declaration in `extern (C/Windows) { declaration }`
                    // Necessary for function pointers, but harmless to apply to all.
                    auto decls = new AST.Dsymbols(1);
                    (*decls)[0] = s;
                    const lkg = specifier.mod & MOD.x__stdcall ? LINK.windows : linkage;
                    s = new AST.LinkDeclaration(s.loc, lkg, decls);
                }
                symbols.push(s);
            }
            if (level == LVL.global && !id)
                error("expected identifier for declaration");

            first = false;

            switch (token.value)
            {
                case TOK.identifier:
                    if (s)
                    {
                        error(token.loc, "missing comma or semicolon after declaration of `%s`, found `%s` instead", s.toChars(), token.toChars());
                        goto Lend;
                    }
                    goto default;

                case TOK.semicolon:
                    nextToken();
                    return;

                case TOK.comma:
                    if (!symbolsSave)
                        symbolsSave = symbols;
                    nextToken();
                    break;

                default:
                    error("`=`, `;` or `,` expected to end declaration instead of `%s`", token.toChars());
                Lend:
                    return scanPastSemicolon();
            }
        }
    }

    /***************************************
     * C11 Function Definitions
     * function-definition
     *    declaration-specifiers declarator declaration-list (opt) compound-statement
     *
     * declaration-list:
     *    declaration
     *    declaration-list declaration
     *
     * It's already been parsed up to the declaration-list (opt).
     * Pick it up from there.
     * Params:
     *    id = function identifier
     *    ft = function type
     *    specifier = function specifiers
     * Returns:
     *  Dsymbol for the function
     */
    AST.Dsymbol cparseFunctionDefinition(Identifier id, AST.TypeFunction ft, ref Specifier specifier)
    {
        /* Start function scope
         */
        typedefTab.push(null);

        if (token.value != TOK.leftCurly)       // if not start of a compound-statement
        {
            // Do declaration-list
            do
            {
                cparseDeclaration(LVL.parameter);
            } while (token.value != TOK.leftCurly);

            /* Since there were declarations, the parameter-list must have been
             * an identifier-list.
             */
            ft.parameterList.hasIdentifierList = true;        // semantic needs to know to adjust parameter types
            auto pl = ft.parameterList;
            if (pl.varargs != AST.VarArg.none && pl.length)
                error("function identifier-list cannot end with `...`");
            ft.parameterList.varargs = AST.VarArg.KRvariadic;   // but C11 allows extra arguments
            auto plLength = pl.length;
            if (symbols && symbols.length != plLength)
                error(token.loc, "%d identifiers does not match %d declarations", cast(int)plLength, cast(int)symbols.length);

            /* Transfer the types and storage classes from symbols[] to pl[]
             */
            foreach (i; 0 .. plLength)
            {
                auto p = pl[i];  // yes, quadratic

                // Convert typedef-identifier to identifier
                if (p.type)
                {
                    if (auto t = p.type.isTypeIdentifier())
                    {
                        p.ident = t.ident;
                        p.type = null;
                    }
                }

                if (p.type || !(p.storageClass & STC.parameter))
                    error("storage class and type are not allowed in identifier-list");
                if (!symbols)
                {
                    // Error already given in cparseDeclaration
                    p.type = AST.Type.terror;
                    continue;
                }
                foreach (s; (*symbols)[]) // yes, quadratic
                {
                    auto ad = s.isAttribDeclaration();
                    if (ad)
                        s = (*ad.decl)[0];      // AlignDeclaration wrapping the declaration

                    auto d = s.isDeclaration();
                    if (d && p.ident == d.ident && d.type)
                    {
                        p.type = d.type;
                        p.storageClass = d.storage_class;
                        d.type = null; // don't reuse
                        break;
                    }
                }
                if (!p.type)
                {
                    error("no declaration for identifier `%s`", p.ident.toChars());
                    p.type = AST.Type.terror;
                }
            }
        }

        addFuncName = false;    // gets set to true if somebody references __func__ in this function
        const locFunc = token.loc;

        auto body = cparseStatement(ParseStatementFlags.curly);  // don't start a new scope; continue with parameter scope
        typedefTab.pop();                                        // end of function scope

        StorageClass stc = specifiersToSTC(LVL.global, specifier);
        stc &= ~STC.gshared;    // no gshared functions
        auto fd = new AST.FuncDeclaration(locFunc, prevloc, id, stc, ft, specifier.noreturn);
        specifiersToFuncDeclaration(fd, specifier);

        if (addFuncName)
        {
            auto s = createFuncName(locFunc, id);
            body = new AST.CompoundStatement(locFunc, s, body);
        }
        fd.fbody = body;

        // TODO add `symbols` to the function's local symbol table `sc2` in FuncDeclaration::semantic3()

        return fd;
    }

    /***************************************
     * C11 Initialization
     * initializer:
     *    assignment-expression
     *    { }                       // C23 6.7.10 addition
     *    { initializer-list }
     *    { initializer-list , }
     *
     * initializer-list:
     *    designation (opt) initializer
     *    initializer-list , designation (opt) initializer
     *
     * designation:
     *    designator-list =
     *
     * designator-list:
     *    designator
     *    designator-list designator
     *
     * designator:
     *    [ constant-expression ]
     *    . identifier
     * Returns:
     *    initializer
     */
    AST.Initializer cparseInitializer()
    {
        if (token.value != TOK.leftCurly)
        {
            auto ae = cparseAssignExp();        // assignment-expression
            return new AST.ExpInitializer(token.loc, ae);
        }
        nextToken();
        const loc = token.loc;

        if (token.value == TOK.rightCurly)      // { }
        {
            nextToken();
            return new AST.DefaultInitializer(loc);
        }

        /* Collect one or more `designation (opt) initializer`
         * into ci.initializerList, but lazily create ci
         */
        AST.CInitializer ci;
        while (1)
        {
            /* There can be 0 or more designators preceding an initializer.
             * Collect them in desigInit
             */
            AST.DesigInit desigInit;
            while (1)
            {
                if (token.value == TOK.leftBracket)     // [ constant-expression ]
                {
                    nextToken();
                    auto e = cparseConstantExp();
                    check(TOK.rightBracket);
                    if (!desigInit.designatorList)
                        desigInit.designatorList = new AST.Designators;
                    desigInit.designatorList.push(AST.Designator(e));
                }
                else if (token.value == TOK.dot)        // . identifier
                {
                    nextToken();
                    if (token.value != TOK.identifier)
                    {
                        error("identifier expected following `.` designator");
                        break;
                    }
                    if (!desigInit.designatorList)
                        desigInit.designatorList = new AST.Designators;
                    desigInit.designatorList.push(AST.Designator(token.ident));
                    nextToken();
                }
                else
                {
                    if (desigInit.designatorList)
                        check(TOK.assign);
                    break;
                }
            }

            desigInit.initializer = cparseInitializer();
            if (!ci)
                ci = new AST.CInitializer(loc);
            ci.initializerList.push(desigInit);
            if (token.value == TOK.comma)
            {
                nextToken();
                if (token.value != TOK.rightCurly)
                    continue;
            }
            break;
        }
        check(TOK.rightCurly);
        //printf("ci: %s\n", ci.toChars());
        return ci;
    }

    /*************************************
     * C11 6.7
     * declaration-specifier:
     *    storage-class-specifier declaration-specifiers (opt)
     *    type-specifier declaration-specifiers (opt)
     *    type-qualifier declaration-specifiers (opt)
     *    function-specifier declaration-specifiers (opt)
     *    alignment-specifier declaration-specifiers (opt)
     * Params:
     *  level = declaration context
     *  specifier = specifiers in and out
     * Returns:
     *  resulting type, null if not specified
     */
    private AST.Type cparseDeclarationSpecifiers(LVL level, ref Specifier specifier)
    {
        enum TKW : uint
        {
            xnone      = 0,
            xchar      = 1,
            xsigned    = 2,
            xunsigned  = 4,
            xshort     = 8,
            xint       = 0x10,
            xlong      = 0x20,
            xllong     = 0x40,
            xfloat     = 0x80,
            xdouble    = 0x100,
            xldouble   = 0x200,
            xtag       = 0x400,
            xident     = 0x800,
            xvoid      = 0x1000,
            xbool      = 0x4000,
            ximaginary = 0x8000,
            xcomplex   = 0x10000,
            x_Atomic   = 0x20000,
            xint128    = 0x40000,
        }

        AST.Type t;
        Loc loc;
        //printf("parseDeclarationSpecifiers()\n");

        TKW tkw;
        SCW scw = specifier.scw & SCW.xtypedef;
        MOD mod;
        Identifier id;
        Identifier previd;

    Lwhile:
        while (1)
        {
            //printf("token %s\n", token.toChars());
            TKW tkwx;
            SCW scwx;
            MOD modx;
            switch (token.value)
            {
                // Storage class specifiers
                case TOK.static_:    scwx = SCW.xstatic;    break;
                case TOK.extern_:    scwx = SCW.xextern;    break;
                case TOK.auto_:      scwx = SCW.xauto;      break;
                case TOK.register:   scwx = SCW.xregister;  break;
                case TOK.typedef_:   scwx = SCW.xtypedef;   break;
                case TOK.inline:     scwx = SCW.xinline;    break;
                case TOK._Noreturn:  scwx = SCW.x_Noreturn; break;
                case TOK.__thread:
                case TOK._Thread_local: scwx = SCW.x_Thread_local; break;

                // Type qualifiers
                case TOK.const_:     modx = MOD.xconst;     break;
                case TOK.volatile:   modx = MOD.xvolatile;  break;
                case TOK.restrict:   modx = MOD.xrestrict;  break;
                case TOK.__stdcall:  modx = MOD.x__stdcall; break;

                // Type specifiers
                case TOK.char_:      tkwx = TKW.xchar;      break;
                case TOK.signed:     tkwx = TKW.xsigned;    break;
                case TOK.unsigned:   tkwx = TKW.xunsigned;  break;
                case TOK.int16:      tkwx = TKW.xshort;     break;
                case TOK.int32:      tkwx = TKW.xint;       break;
                case TOK.int64:      tkwx = TKW.xlong;      break;
                case TOK.__int128:   tkwx = TKW.xint128;    break;
                case TOK.float32:    tkwx = TKW.xfloat;     break;
                case TOK.float64:    tkwx = TKW.xdouble;    break;
                case TOK.void_:      tkwx = TKW.xvoid;      break;
                case TOK._Bool:      tkwx = TKW.xbool;      break;
                case TOK._Imaginary: tkwx = TKW.ximaginary; break;
                case TOK._Complex:   tkwx = TKW.xcomplex;   break;

                case TOK.identifier:
                    tkwx = TKW.xident;
                    id = token.ident;
                    break;

                case TOK.struct_:
                case TOK.union_:
                {
                    const structOrUnion = token.value;
                    const sloc = token.loc;
                    nextToken();

                    Specifier tagSpecifier;

                    /* GNU Extensions
                     * struct-or-union-specifier:
                     *    struct-or-union gnu-attributes (opt) identifier (opt) { struct-declaration-list } gnu-attributes (opt)
                     *    struct-or-union gnu-attribute (opt) identifier
                     */
                    while (1)
                    {
                        if (token.value == TOK.__attribute__)
                            cparseGnuAttributes(tagSpecifier);
                        else if (token.value == TOK.__declspec)
                            cparseDeclspec(tagSpecifier);
                        else if (token.value == TOK.__pragma)
                            uupragmaDirective(sloc);
                        else
                            break;
                    }
                    t = cparseStruct(sloc, structOrUnion, tagSpecifier.packalign, symbols);
                    tkwx = TKW.xtag;
                    break;
                }

                case TOK.enum_:
                    t = cparseEnum(symbols);
                    tkwx = TKW.xtag;
                    break;

                case TOK._Atomic:
                {
                    // C11 6.7.2.4
                    // type-specifier if followed by `( type-name )`
                    auto tk = peek(&token);
                    if (tk.value == TOK.leftParenthesis)
                    {
                        tk = peek(tk);
                        if (isTypeName(tk) && tk.value == TOK.rightParenthesis)
                        {
                            nextToken();
                            nextToken();
                            t = cparseTypeName();
                            tkwx = TKW.x_Atomic;
                            break;
                        }
                    }
                    // C11 6.7.3 type-qualifier if not
                    modx = MOD.x_Atomic;
                    break;
                }

                case TOK._Alignas:
                {
                    /* C11 6.7.5
                     * _Alignas ( type-name )
                     * _Alignas ( constant-expression )
                     */

                    if (level & (LVL.parameter | LVL.prototype))
                        error("no alignment-specifier for parameters"); // C11 6.7.5-2

                    nextToken();
                    check(TOK.leftParenthesis);
                    AST.Expression exp;
                    auto tk = &token;
                    if (isTypeName(tk))  // _Alignas ( type-name )
                    {
                        auto talign = cparseTypeName();
                        /* Convert type to expression: `talign.alignof`
                         */
                        auto e = new AST.TypeExp(loc, talign);
                        exp = new AST.DotIdExp(loc, e, Id.__xalignof);
                    }
                    else  // _Alignas ( constant-expression )
                    {
                        exp = cparseConstantExp();
                    }

                    if (!specifier.alignExps)
                        specifier.alignExps = new AST.Expressions(0);
                    specifier.alignExps.push(exp);

                    check(TOK.rightParenthesis);
                    break;
                }

                case TOK.__attribute__:
                {
                    /* GNU Extensions
                     * declaration-specifiers:
                     *    gnu-attributes declaration-specifiers (opt)
                     */
                    cparseGnuAttributes(specifier);
                    break;
                }

                case TOK.__declspec:
                {
                    /* Microsoft extension
                     */
                    cparseDeclspec(specifier);
                    break;
                }

                case TOK.typeof_:
                {
                    nextToken();
                    check(TOK.leftParenthesis);

                    auto tk = &token;
                    AST.Expression e;
                    if (isTypeName(tk))
                        e = new AST.TypeExp(loc, cparseTypeName());
                    else
                        e = cparseExpression();
                    t = new AST.TypeTypeof(loc, e);

                    if(token.value == TOK.rightParenthesis)
                        nextToken();
                    else
                    {
                        t = AST.Type.terror;
                        error("`typeof` operator expects an expression or type name in parentheses");

                        // skipParens et. al expect to be on the opening parenthesis
                        int parens;
                        loop: while(1)
                        {
                            switch(token.value)
                            {
                                case TOK.leftParenthesis:
                                    parens++;
                                    break;
                                case TOK.rightParenthesis:
                                    parens--;
                                    if(parens < 0)
                                        goto case;
                                    break;
                                case TOK.endOfFile:
                                    break loop;
                                default:
                            }
                            nextToken();
                        }
                    }

                    tkwx = TKW.xtag;
                    break;
                }

                default:
                    break Lwhile;
            }

            if (tkwx)
            {
                if (tkw & TKW.xlong && tkwx & TKW.xlong)
                {
                    tkw &= ~TKW.xlong;
                    tkwx = TKW.xllong;
                }
                if (tkw && tkwx & TKW.xident)
                {
                    // 2nd identifier can't be a typedef
                    break Lwhile; // leave parser on the identifier for the following declarator
                }
                else if (tkwx & TKW.xident)
                {
                    // 1st identifier, save it for TypeIdentifier
                    previd = id;
                }
                if (tkw & TKW.xident && tkwx ||  // typedef-name followed by type-specifier
                    tkw & tkwx)                  // duplicate type-specifiers
                {
                    error("illegal combination of type specifiers");
                    tkwx = TKW.init;
                }
                tkw |= tkwx;
                if (!(tkwx & TKW.xtag)) // if parser already advanced
                    nextToken();
                continue;
            }

            if (modx)
            {
                mod |= modx;
                nextToken();
                continue;
            }

            if (scwx)
            {
                if (scw & scwx)
                    error("duplicate storage class");
                scw |= scwx;
                // C11 6.7.1-2 At most one storage-class may be given, except that
                // _Thread_local may appear with static or extern.
                const scw2 = scw & (SCW.xstatic | SCW.xextern | SCW.xauto | SCW.xregister | SCW.xtypedef);
                if (scw2 & (scw2 - 1) ||
                    scw & (SCW.x_Thread_local) && scw & (SCW.xauto | SCW.xregister | SCW.xtypedef))
                {
                    error("multiple storage classes in declaration specifiers");
                    scw &= ~scwx;
                }
                if (level == LVL.local &&
                    scw & (SCW.x_Thread_local) && scw & (SCW.xinline | SCW.x_Noreturn))
                {
                    error("`inline` and `_Noreturn` function specifiers not allowed for `_Thread_local`");
                    scw &= ~scwx;
                }
                if (level == LVL.local &&
                    scw & (SCW.x_Thread_local) && !(scw & (SCW.xstatic | SCW.xextern)))
                {
                    error("`_Thread_local` in block scope must be accompanied with `static` or `extern`"); // C11 6.7.1-3
                    scw &= ~scwx;
                }
                if (level & (LVL.parameter | LVL.prototype) &&
                    scw & ~SCW.xregister)
                {
                    error("only `register` storage class allowed for function parameters");
                    scw &= ~scwx;
                }
                if (level == LVL.global &&
                    scw & (SCW.xauto | SCW.xregister))
                {
                    error("`auto` and `register` storage class not allowed for global");
                    scw &= ~scwx;
                }
                nextToken();
                continue;
            }
        }

        specifier.scw = scw;
        specifier.mod = mod;

        // Convert TKW bits to type t
        switch (tkw)
        {
            case TKW.xnone:                     t = null; break;

            case TKW.xchar:                     t = AST.Type.tchar; break;
            case TKW.xsigned | TKW.xchar:       t = AST.Type.tint8; break;
            case TKW.xunsigned | TKW.xchar:     t = AST.Type.tuns8; break;

            case TKW.xshort:
            case TKW.xsigned | TKW.xshort:
            case TKW.xsigned | TKW.xshort | TKW.xint:
            case TKW.xshort | TKW.xint:         t = integerTypeForSize(shortsize); break;

            case TKW.xunsigned | TKW.xshort | TKW.xint:
            case TKW.xunsigned | TKW.xshort:    t = unsignedTypeForSize(shortsize); break;

            case TKW.xint:
            case TKW.xsigned:
            case TKW.xsigned | TKW.xint:        t = integerTypeForSize(intsize); break;

            case TKW.xunsigned:
            case TKW.xunsigned | TKW.xint:      t = unsignedTypeForSize(intsize); break;

            case TKW.xlong:
            case TKW.xsigned | TKW.xlong:
            case TKW.xsigned | TKW.xlong | TKW.xint:
            case TKW.xlong | TKW.xint:          t = integerTypeForSize(longsize); break;

            case TKW.xunsigned | TKW.xlong | TKW.xint:
            case TKW.xunsigned | TKW.xlong:     t = unsignedTypeForSize(longsize); break;

            case TKW.xllong:
            case TKW.xsigned | TKW.xllong:
            case TKW.xsigned | TKW.xllong | TKW.xint:
            case TKW.xllong | TKW.xint:          t = integerTypeForSize(long_longsize); break;

            case TKW.xunsigned | TKW.xllong | TKW.xint:
            case TKW.xunsigned | TKW.xllong:     t = unsignedTypeForSize(long_longsize); break;

            case TKW.xint128:
            case TKW.xsigned | TKW.xint128:     t = integerTypeForSize(16); break;

            case TKW.xunsigned | TKW.xint128:   t = unsignedTypeForSize(16); break;

            case TKW.xvoid:                     t = AST.Type.tvoid; break;
            case TKW.xbool:                     t = boolsize == 1 ? AST.Type.tbool : integerTypeForSize(boolsize); break;

            case TKW.xfloat:                    t = AST.Type.tfloat32; break;
            case TKW.xdouble:                   t = AST.Type.tfloat64; break;
            case TKW.xlong | TKW.xdouble:       t = realType(RTFlags.realfloat); break;

            case TKW.ximaginary | TKW.xfloat:              t = AST.Type.timaginary32; break;
            case TKW.ximaginary | TKW.xdouble:             t = AST.Type.timaginary64; break;
            case TKW.ximaginary | TKW.xlong | TKW.xdouble: t = realType(RTFlags.imaginary); break;

            case TKW.xcomplex | TKW.xfloat:                t = AST.Type.tcomplex32; break;
            case TKW.xcomplex | TKW.xdouble:               t = AST.Type.tcomplex64; break;
            case TKW.xcomplex | TKW.xlong | TKW.xdouble:   t = realType(RTFlags.complex); break;

            case TKW.xident:
            {
                const idx = previd.toString();
                if (idx.length > 2 && idx[0] == '_' && idx[1] == '_')  // leading double underscore
                    importBuiltins = true;  // probably one of those compiler extensions
                t = null;

                /* Punch through to what the typedef is, to support things like:
                 *  typedef T* T;
                 */
                auto pt = lookupTypedef(previd);
                if (pt && *pt)      // if previd is a known typedef
                    t = *pt;

                if (!t)
                    t = new AST.TypeIdentifier(loc, previd);
                break;
            }

            case TKW.xtag:
            case TKW.x_Atomic:  // no atomics for you
                break;          // t is already set

            default:
                error("illegal type combination");
                t = AST.Type.terror;
                break;
        }

        return t;
    }

    /********************************
     * C11 6.7.6
     * Parse a declarator (including function definitions).
     * declarator:
     *    pointer (opt) direct-declarator
     *
     * direct-declarator :
     *    identifier
     *    ( declarator )
     *    direct-declarator [ type-qualifier-list (opt) assignment-expression (opt) ]
     *    direct-declarator [ static type-qualifier-list (opt) assignment-expression ]
     *    direct-declarator [ type-qualifier-list static assignment-expression (opt) ]
     *    direct-declarator [ type-qualifier-list (opt) * ]
     *    direct-declarator ( parameter-type-list )
     *    direct-declarator ( identifier-list (opt) )
     *
     * pointer :
     *    * type-qualifier-list (opt)
     *    * type-qualifier-list (opt) pointer
     *
     * type-qualifier-list :
     *    type-qualifier
     *    type-qualifier-list type-qualifier
     *
     * parameter-type-list :
     *    parameter-list
     *    parameter-list , ...
     *
     * parameter-list :
     *    parameter-declaration
     *    parameter-list , parameter-declaration
     *
     * parameter-declaration :
     *    declaration-specifiers declarator
     *    declaration-specifiers abstract-declarator (opt)
     *
     * identifier-list :
     *    identifier
     *    identifier-list , identifier
     *
     * Params:
     *  declarator   = declarator kind
     *  tbase        = base type to start with
     *  pident       = set to Identifier if there is one, null if not
     *  specifier    = specifiers in and out
     * Returns:
     *  type declared. If a TypeFunction is returned, this.symbols is the
     *  symbol table for the parameter-type-list, which will contain any
     *  declared struct, union or enum tags.
     */
    private AST.Type cparseDeclarator(DTR declarator, AST.Type tbase,
        out Identifier pident, ref Specifier specifier)
    {
        //printf("cparseDeclarator(%d, %s)\n", declarator, tbase.toChars());
        AST.Types constTypes; // all the Types that will need `const` applied to them

        /* Insert tx -> t into
         *   ts -> ... -> t
         * so that
         *   ts -> ... -> tx -> t
         */
        static void insertTx(ref AST.Type ts, AST.Type tx, AST.Type t)
        {
            AST.Type* pt;
            for (pt = &ts; *pt != t; pt = &(cast(AST.TypeNext)*pt).next)
            {
            }
            *pt = tx;
        }

        AST.Type parseDecl(AST.Type t)
        {
            //printf("parseDecl() t: %s\n", t.toChars());
            AST.Type ts;
            while (1)
            {
                switch (token.value)
                {
                case TOK.identifier:        // identifier
                    //printf("identifier %s\n", token.ident.toChars());
                    if (declarator == DTR.xabstract)
                        error("identifier not allowed in abstract-declarator");
                    pident = token.ident;
                    ts = t;
                    nextToken();
                    break;

                case TOK.leftParenthesis:   // ( declarator )
                    //printf("leftParen\n");
                    /* like: T (*fp)();
                     *       T ((*fp))();
                     */
                    auto tk = &token;
                    if (!isCDeclarator(tk, declarator))
                    {
                        /* Not ( declarator ), might be parameter-list
                         */
                        ts = t;
                        break;
                    }
                    nextToken();

                    if (token.value == TOK.__stdcall) // T (__stdcall*fp)();
                    {
                        specifier.mod |= MOD.x__stdcall;
                        nextToken();
                    }

                    ts = parseDecl(t);
                    check(TOK.rightParenthesis);
                    break;

                case TOK.mul:               // pointer
                    //printf("star\n");
                    t = new AST.TypePointer(t);
                    nextToken();
                    // add post fixes const/volatile/restrict/_Atomic
                    const mod = cparseTypeQualifierList();
                    if (mod & MOD.xconst)
                        constTypes.push(t);
                    if (token.value == TOK.__attribute__)
                        cparseGnuAttributes(specifier);
                    continue;

                default:
                    //printf("default %s\n", token.toChars());
                    if (declarator == DTR.xdirect)
                    {
                        if (!t || t.isTypeIdentifier())
                        {
                            // const arr[1];
                            error("no type-specifier for declarator");
                            t = AST.Type.tint32;
                        }
                        else
                            error("identifier or `(` expected"); // )
                        panic();
                    }
                    ts = t;
                    break;
                }
                break;
            }

            // parse DeclaratorSuffixes
            while (1)
            {
                switch (token.value)
                {
                    case TOK.leftBracket:
                    {
                        // post [] syntax, pick up any leading type qualifiers, `static` and `*`
                        AST.Type ta;
                        nextToken();

                        auto mod = cparseTypeQualifierList();   // const/volatile/restrict/_Atomic

                        bool isStatic;
                        bool isVLA;
                        if (token.value == TOK.static_)
                        {
                            isStatic = true;    // `static`
                            nextToken();
                            if (!mod)           // type qualifiers after `static`
                                mod = cparseTypeQualifierList();
                        }
                        else if (token.value == TOK.mul)
                        {
                            if (peekNext() == TOK.rightBracket)
                            {
                                isVLA = true;   // `*`
                                nextToken();
                            }
                        }

                        if (isStatic || token.value != TOK.rightBracket)
                        {
                            //printf("It's a static array\n");
                            AST.Expression e = cparseAssignExp(); // [ expression ]
                            ta = new AST.TypeSArray(t, e);
                        }
                        else
                        {
                            /* C11 6.7.6.2-4 An [ ] array is an incomplete array type
                             */
                            ta = new AST.TypeSArray(t);
                        }
                        check(TOK.rightBracket);

                        // Issue errors for unsupported types.
                        if (isVLA) // C11 6.7.6.2
                        {
                            error("variable length arrays are not supported");
                        }
                        if (isStatic) // C11 6.7.6.3
                        {
                            error("static array parameters are not supported");
                        }
                        if (declarator != DTR.xparameter)
                        {
                            /* C11 6.7.6.2-4: '*' can only be used with function prototype scope.
                             */
                            if (isVLA)
                                error("variable length array used outside of function prototype");
                            /* C11 6.7.6.2-1: type qualifiers and 'static' shall only appear
                             * in a declaration of a function parameter with an array type.
                             */
                            if (isStatic || mod)
                                error("static or type qualifier used outside of function prototype");
                        }
                        if (ts.isTypeSArray() || ts.isTypeDArray())
                        {
                            /* C11 6.7.6.2-1: type qualifiers and 'static' shall only appear
                             * in the outermost array type derivation.
                             */
                            if (isStatic || mod)
                                error("static or type qualifier used in non-outermost array type derivation");
                            /* C11 6.7.6.2-1: the element type shall not be an incomplete or
                             * function type.
                             */
                            if (ta.isTypeSArray() && ta.isTypeSArray().isIncomplete() && !isVLA)
                                error("array type has incomplete element type `%s`", ta.toChars());
                        }

                        // Apply type qualifiers to the constructed type.
                        if (mod & MOD.xconst) // ignore the other bits
                            ta = toConst(ta);
                        insertTx(ts, ta, t);  // ts -> ... -> ta -> t
                        continue;
                    }

                    case TOK.leftParenthesis:
                    {
                        // New symbol table for parameter-list
                        auto symbolsSave = this.symbols;
                        this.symbols = null;

                        auto parameterList = cparseParameterList();
                        const lkg = specifier.mod & MOD.x__stdcall ? LINK.windows : linkage;
                        StorageClass stc = specifier._nothrow ? STC.nothrow_ : 0;
                        if (specifier._pure)
                            stc |= STC.pure_;
                        stc |= defaultStorageClasses;
                        AST.Type tf = new AST.TypeFunction(parameterList, t, lkg, stc);
                        //tf = tf.addSTC(storageClass);  // TODO
                        insertTx(ts, tf, t);  // ts -> ... -> tf -> t

                        if (ts != tf)
                            this.symbols = symbolsSave;
                        break;
                    }

                    default:
                        break;
                }
                break;
            }
            if (declarator == DTR.xdirect && !pident)
                error("expected identifier for declarator");
            return ts;
        }

        auto t = parseDecl(tbase);

        if (specifier.vector_size)
        {
            auto length = new AST.IntegerExp(token.loc, specifier.vector_size / tbase.size(), AST.Type.tuns32);
            auto tsa = new AST.TypeSArray(tbase, length);
            AST.Type tv = new AST.TypeVector(tsa);
            specifier.vector_size = 0;          // used it up

            insertTx(t, tv, tbase);     // replace tbase with tv
        }

        /* Because const is transitive, cannot assemble types from
         * fragments. Instead, types to be annotated with const are put
         * in constTypes[], and a bottom up scan of t is done to apply
         * const
         */
        if (constTypes.length)
        {
            AST.Type constApply(AST.Type t)
            {
                if (t.nextOf())
                {
                    auto tn = cast(AST.TypeNext)t; // t.nextOf() should return a ref instead of this
                    tn.next = constApply(tn.next);
                }
                foreach (tc; constTypes[])
                {
                    if (tc is t)
                    {
                        return toConst(t);
                    }
                }
                return t;
            }

            if (declarator == DTR.xparameter &&
                t.isTypePointer())
            {
                /* Because there are instances in .h files of "const pointer to mutable",
                 * skip applying transitive `const`
                 * https://issues.dlang.org/show_bug.cgi?id=22534
                 */
                auto tn = cast(AST.TypeNext)t;
                tn.next = constApply(tn.next);
            }
            else
                t = constApply(t);
        }

        //printf("result: %s\n", t.toChars());
        return t;
    }

    /******************************
     * C11 6.7.3
     * type-qualifier:
     *    const
     *    restrict
     *    volatile
     *    _Atomic
     *    __stdcall
     */
    MOD cparseTypeQualifierList()
    {
        MOD mod;
        while (1)
        {
            switch (token.value)
            {
                case TOK.const_:     mod |= MOD.xconst;     break;
                case TOK.volatile:   mod |= MOD.xvolatile;  break;
                case TOK.restrict:   mod |= MOD.xrestrict;  break;
                case TOK._Atomic:    mod |= MOD.x_Atomic;   break;
                case TOK.__stdcall:  mod |= MOD.x__stdcall; break;

                default:
                    return mod;
            }
            nextToken();
        }
    }

    /***********************************
     * C11 6.7.7
     */
    AST.Type cparseTypeName()
    {
        Specifier specifier;
        specifier.packalign.setDefault();
        auto tspec = cparseSpecifierQualifierList(LVL.global, specifier);
        if (!tspec)
        {
            error("type-specifier is missing");
            tspec = AST.Type.tint32;
        }
        if (tspec && specifier.mod & MOD.xconst)
        {
            tspec = toConst(tspec);
            specifier.mod = MOD.xnone;      // 'used' it
        }
        Identifier id;
        return cparseDeclarator(DTR.xabstract, tspec, id, specifier);
    }

    /***********************************
     * C11 6.7.2.1
     * specifier-qualifier-list:
     *    type-specifier specifier-qualifier-list (opt)
     *    type-qualifier specifier-qualifier-list (opt)
     * Params:
     *  level = declaration context
     *  specifier = specifiers in and out
     * Returns:
     *  resulting type, null if not specified
     */
    AST.Type cparseSpecifierQualifierList(LVL level, ref Specifier specifier)
    {
        auto t = cparseDeclarationSpecifiers(level, specifier);
        if (specifier.scw)
            error("storage class not allowed in specifier-qualified-list");
        return t;
    }

    /***********************************
     * C11 6.7.6.3
     * ( parameter-type-list )
     * ( identifier-list (opt) )
     */
    AST.ParameterList cparseParameterList()
    {
        auto parameters = new AST.Parameters();
        AST.VarArg varargs = AST.VarArg.none;
        StorageClass varargsStc;

        check(TOK.leftParenthesis);
        if (token.value == TOK.void_ && peekNext() == TOK.rightParenthesis) // func(void)
        {
            nextToken();
            nextToken();
            return AST.ParameterList(parameters, varargs, varargsStc);
        }

        if (token.value == TOK.rightParenthesis)        // func()
        {
            nextToken();
            return AST.ParameterList(parameters, AST.VarArg.KRvariadic, varargsStc);
        }

        /* Create function prototype scope
         */
        typedefTab.push(null);

        AST.ParameterList finish()
        {
            typedefTab.pop();
            return AST.ParameterList(parameters, varargs, varargsStc);
        }

        /* The check for identifier-list comes later,
         * when doing the trailing declaration-list (opt)
         */
        while (1)
        {
            if (token.value == TOK.rightParenthesis)
                break;
            if (token.value == TOK.dotDotDot)
            {
                if (parameters.length == 0)     // func(...)
                    error("named parameter required before `...`");
                importBuiltins = true;          // will need __va_list_tag
                varargs = AST.VarArg.variadic;  // C-style variadics
                nextToken();
                check(TOK.rightParenthesis);
                return finish();
            }

            Specifier specifier;
            specifier.packalign.setDefault();
            auto tspec = cparseDeclarationSpecifiers(LVL.prototype, specifier);
            if (!tspec)
            {
                error("no type-specifier for parameter");
                tspec = AST.Type.tint32;
            }

            if (specifier.mod & MOD.xconst)
            {
                if ((token.value == TOK.rightParenthesis || token.value == TOK.comma) &&
                    tspec.isTypeIdentifier())
                    error("type-specifier omitted for parameter `%s`", tspec.isTypeIdentifier().ident.toChars());

                tspec = toConst(tspec);
                specifier.mod = MOD.xnone;      // 'used' it
            }

            Identifier id;
            const paramLoc = token.loc;
            auto t = cparseDeclarator(DTR.xparameter, tspec, id, specifier);
            if (token.value == TOK.__attribute__)
                cparseGnuAttributes(specifier);
            if (specifier.mod & MOD.xconst)
                t = toConst(t);
            auto param = new AST.Parameter(paramLoc, specifiersToSTC(LVL.parameter, specifier),
                                           t, id, null, null);
            parameters.push(param);
            if (token.value == TOK.rightParenthesis || token.value == TOK.endOfFile)
                break;
            check(TOK.comma);
        }
        check(TOK.rightParenthesis);
        return finish();
    }

    /***********************************
     * C11 6.7.10
     * _Static_assert ( constant-expression , string-literal ) ;
     */
    private AST.StaticAssert cparseStaticAssert()
    {
        const loc = token.loc;

        //printf("cparseStaticAssert()\n");
        nextToken();
        check(TOK.leftParenthesis);
        auto exp = cparseConstantExp();
        check(TOK.comma);
        if (token.value != TOK.string_)
            error("string literal expected");
        auto msg = cparsePrimaryExp();
        check(TOK.rightParenthesis);
        check(TOK.semicolon);
        return new AST.StaticAssert(loc, exp, msg);
    }

    /*************************
     * Collect argument list.
     * Parser is on opening parenthesis.
     * Returns:
     *    the arguments
     */
    private AST.Expressions* cparseArguments()
    {
        nextToken();
        auto arguments = new AST.Expressions();
        while (token.value != TOK.rightParenthesis && token.value != TOK.endOfFile)
        {
            auto arg = cparseAssignExp();
            arguments.push(arg);
            if (token.value != TOK.comma)
                break;

            nextToken(); // consume comma
        }

        check(TOK.rightParenthesis);

        return arguments;
    }

    /*************************
     * __declspec parser
     * https://docs.microsoft.com/en-us/cpp/cpp/declspec
     * decl-specifier:
     *    __declspec ( extended-decl-modifier-seq )
     *
     * extended-decl-modifier-seq:
     *    extended-decl-modifier (opt)
     *    extended-decl-modifier extended-decl-modifier-seq
     *
     * extended-decl-modifier:
     *    align(number)
     *    deprecated(depMsg)
     *    dllimport
     *    dllexport
     *    naked
     *    noinline
     *    noreturn
     *    nothrow
     *    thread
     * Params:
     *  specifier = filled in with the attribute(s)
     */
    private void cparseDeclspec(ref Specifier specifier)
    {
        //printf("cparseDeclspec()\n");
        /* Check for dllexport, dllimport
         * Ignore the rest
         */
        nextToken();     // move past __declspec
        check(TOK.leftParenthesis);
        while (1)
        {
            if (token.value == TOK.rightParenthesis)
            {
                nextToken();
                break;
            }
            else if (token.value == TOK.endOfFile)
                break;
            else if (token.value == TOK.identifier)
            {
                if (token.ident == Id.dllimport)
                {
                    specifier.dllimport = true;
                    nextToken();
                }
                else if (token.ident == Id.dllexport)
                {
                    specifier.dllexport = true;
                    nextToken();
                }
                else if (token.ident == Id.naked)
                {
                    specifier.naked = true;
                    nextToken();
                }
                else if (token.ident == Id.noinline)
                {
                    specifier.scw |= SCW.xnoinline;
                    nextToken();
                }
                else if (token.ident == Id.noreturn)
                {
                    specifier.noreturn = true;
                    nextToken();
                }
                else if (token.ident == Id._nothrow)
                {
                    specifier._nothrow = true;
                    nextToken();
                }
                else if (token.ident == Id.thread)
                {
                    specifier.scw |= SCW.x_Thread_local;
                    nextToken();
                }
                else if (token.ident == Id._align)
                {
                    // Microsoft spec is very imprecise as to how this actually works
                    nextToken();
                    check(TOK.leftParenthesis);
                    if (token.value == TOK.int32Literal)
                    {
                        const n = token.unsvalue;
                        if (n < 1 || n & (n - 1) || 8192 < n)
                            error("__decspec(align(%lld)) must be an integer positive power of 2 and be <= 8,192", cast(ulong)n);
                        specifier.packalign.set(cast(uint)n);
                        specifier.packalign.setPack(true);
                        nextToken();
                    }
                    else
                    {
                        error("alignment value expected, not `%s`", token.toChars());
                        nextToken();
                    }

                    check(TOK.rightParenthesis);
                }
                else if (token.ident == Id._deprecated)
                {
                    specifier._deprecated = true;
                    nextToken();
                    if (token.value == TOK.leftParenthesis)  // optional deprecation message
                    {
                        nextToken();
                        specifier.depMsg = cparseExpression();
                        check(TOK.rightParenthesis);
                    }
                }
                else
                {
                    nextToken();
                    if (token.value == TOK.leftParenthesis)
                        cparseParens();
                }
            }
            else if (token.value == TOK.restrict) // ImportC assigns no semantics to `restrict`, so just ignore the keyword.
                nextToken();
            else
            {
                error("extended-decl-modifier expected after `__declspec(`, saw `%s` instead", token.toChars());
                nextToken();
                if (token.value != TOK.rightParenthesis)
                    break;
            }
        }
    }

    /*************************
     * Parser for asm label. It appears after the declarator, and has apparently
     * nothing to do with inline assembler.
     * https://gcc.gnu.org/onlinedocs/gcc/Asm-Labels.html
     * simple-asm-expr:
     *   asm ( asm-string-literal )
     *
     * asm-string-literal:
     *   string-literal
     */
    private AST.StringExp cparseGnuAsmLabel()
    {
        nextToken();     // move past asm
        check(TOK.leftParenthesis);
        if (token.value != TOK.string_)
            error("string literal expected for Asm Label, not `%s`", token.toChars());
        auto label = cparsePrimaryExp();
        check(TOK.rightParenthesis);
        return cast(AST.StringExp) label;
    }

    /********************
     * Parse C inline assembler statement in Gnu format.
     * https://gcc.gnu.org/onlinedocs/gcc/Extended-Asm.html
     *   asm asm-qualifiers ( AssemblerTemplate : OutputOperands : InputOperands : Clobbers : GotoLabels )
     * Current token is on the `asm`.
     * Returns:
     *   inline assembler expression as a Statement
     */
    private AST.Statement cparseGnuAsm()
    {
        // Defer parsing of AsmStatements until semantic processing.
        const loc = token.loc;

        nextToken();

        // Consume all asm-qualifiers. As a future optimization, we could record
        // the `inline` and `volatile` storage classes against the statement.
        while (token.value == TOK.goto_ ||
               token.value == TOK.inline ||
               token.value == TOK.volatile)
            nextToken();

        check(TOK.leftParenthesis);
        if (token.value != TOK.string_)
            error("string literal expected for Assembler Template, not `%s`", token.toChars());
        Token* toklist = null;
        Token** ptoklist = &toklist;
        //Identifier label = null;
        auto statements = new AST.Statements();

        int parens;
        while (1)
        {
            switch (token.value)
            {
                case TOK.leftParenthesis:
                    ++parens;
                    goto default;

                case TOK.rightParenthesis:
                    --parens;
                    if (parens >= 0)
                        goto default;
                    break;

                case TOK.semicolon:
                    error("matching `)` expected, not `;`");
                    break;

                case TOK.endOfFile:
                    /* ( */
                    error("matching `)` expected, not end of file");
                    break;

                case TOK.colonColon:  // treat as two separate : tokens for iasmgcc
                    *ptoklist = allocateToken();
                    memcpy(*ptoklist, &token, Token.sizeof);
                    (*ptoklist).value = TOK.colon;
                    ptoklist = &(*ptoklist).next;

                    *ptoklist = allocateToken();
                    memcpy(*ptoklist, &token, Token.sizeof);
                    (*ptoklist).value = TOK.colon;
                    ptoklist = &(*ptoklist).next;

                    *ptoklist = null;
                    nextToken();
                    continue;

                default:
                    *ptoklist = allocateToken();
                    memcpy(*ptoklist, &token, Token.sizeof);
                    ptoklist = &(*ptoklist).next;
                    *ptoklist = null;
                    nextToken();
                    continue;
            }
            if (toklist)
            {
                // Create AsmStatement from list of tokens we've saved
                AST.Statement s = new AST.AsmStatement(token.loc, toklist);
                statements.push(s);
            }
            break;
        }
        nextToken();
        auto s = new AST.CompoundAsmStatement(loc, statements, 0);
        return s;
    }

    /*************************
     * __attribute__ parser
     * https://gcc.gnu.org/onlinedocs/gcc/Attribute-Syntax.html
     * gnu-attributes:
     *   gnu-attributes gnu-attribute-specifier
     *
     * gnu-attribute-specifier:
     *    __attribute__ (( gnu-attribute-list ))
     *
     * gnu-attribute-list:
     *    gnu-attribute (opt)
     *    gnu-attribute-list , gnu-attribute
     *
     * Params:
     *  specifier = filled in with the attribute(s)
     */
    private void cparseGnuAttributes(ref Specifier specifier)
    {
        while (token.value == TOK.__attribute__)
        {
            nextToken();     // move past __attribute__
            check(TOK.leftParenthesis);
            check(TOK.leftParenthesis);

            if (token.value != TOK.rightParenthesis)
            {
                while (1)
                {
                    cparseGnuAttribute(specifier);
                    if (token.value != TOK.comma)
                        break;
                    nextToken();
                }
            }

            check(TOK.rightParenthesis);
            check(TOK.rightParenthesis);
        }
    }

    /*************************
     * Parse a single GNU attribute
     * gnu-attribute:
     *    gnu-attribute-name
     *    gnu-attribute-name ( identifier )
     *    gnu-attribute-name ( identifier , expression-list )
     *    gnu-attribute-name ( expression-list (opt) )
     *
     * gnu-attribute-name:
     *    keyword
     *    identifier
     *
     * expression-list:
     *    constant-expression
     *    expression-list , constant-expression
     *
     * Params:
     *  specifier = filled in with the attribute(s)
     */
    private void cparseGnuAttribute(ref Specifier specifier)
    {
        /* Check for dllimport, dllexport, naked, noreturn, vector_size(bytes)
         * Ignore the rest
         */
        if (!isGnuAttributeName())
            return;

        if (token.value == TOK.identifier)
        {
            if (token.ident == Id.aligned)
            {
                nextToken();
                if (token.value == TOK.leftParenthesis)
                {
                    nextToken();
                    if (token.value == TOK.int32Literal)
                    {
                        const n = token.unsvalue;
                        if (n < 1 || n & (n - 1) || ushort.max < n)
                            error("__attribute__((aligned(%lld))) must be an integer positive power of 2 and be <= 32,768", cast(ulong)n);
                        specifier.packalign.set(cast(uint)n);
                        specifier.packalign.setPack(true);
                        nextToken();
                    }
                    else
                    {
                        error("alignment value expected, not `%s`", token.toChars());
                        nextToken();
                    }

                    check(TOK.rightParenthesis);
                }
                /* ignore __attribute__((aligned)), which sets the alignment to the largest value for any data
                 * type on the target machine. It's the opposite of __attribute__((packed))
                 */
            }
            else if (token.ident == Id.packed)
            {
                specifier.packalign.set(1);
                specifier.packalign.setPack(true);
                nextToken();
            }
            else if (token.ident == Id.always_inline) // https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html
            {
                specifier.scw |= SCW.xinline;
                nextToken();
            }
            else if (token.ident == Id._deprecated)
            {
                specifier._deprecated = true;
                nextToken();
                if (token.value == TOK.leftParenthesis)  // optional deprecation message
                {
                    nextToken();
                    specifier.depMsg = cparseExpression();
                    check(TOK.rightParenthesis);
                }
            }
            else if (token.ident == Id.dllimport)
            {
                specifier.dllimport = true;
                nextToken();
            }
            else if (token.ident == Id.dllexport)
            {
                specifier.dllexport = true;
                nextToken();
            }
            else if (token.ident == Id.naked)
            {
                specifier.naked = true;
                nextToken();
            }
            else if (token.ident == Id.noinline)
            {
                specifier.scw |= SCW.xnoinline;
                nextToken();
            }
            else if (token.ident == Id.noreturn)
            {
                specifier.noreturn = true;
                nextToken();
            }
            else if (token.ident == Id._nothrow)
            {
                specifier._nothrow = true;
                nextToken();
            }
            else if (token.ident == Id._pure)
            {
                specifier._pure = true;
                nextToken();
            }
            else if (token.ident == Id.vector_size)
            {
                nextToken();
                check(TOK.leftParenthesis);
                if (token.value == TOK.int32Literal)
                {
                    const n = token.unsvalue;
                    if (n < 1 || n & (n - 1) || ushort.max < n)
                        error("__attribute__((vector_size(%lld))) must be an integer positive power of 2 and be <= 32,768", cast(ulong)n);
                    specifier.vector_size = cast(uint) n;
                    nextToken();
                }
                else
                {
                    error("value for vector_size expected, not `%s`", token.toChars());
                    nextToken();
                }
                check(TOK.rightParenthesis);
            }
            else
            {
                nextToken();
                if (token.value == TOK.leftParenthesis)
                    cparseParens();
            }
        }
        else
        {
            nextToken();
            if (token.value == TOK.leftParenthesis)
                cparseParens();
        }
    }

    /*************************
     * See if match for GNU attribute name, which may be any identifier,
     * storage-class-specifier, type-specifier, or type-qualifier.
     * Returns:
     *  true if a valid GNU attribute name
     */
    private bool isGnuAttributeName()
    {
        switch (token.value)
        {
            case TOK.identifier:
            case TOK.static_:
            case TOK.unsigned:
            case TOK.int64:
            case TOK.const_:
            case TOK.extern_:
            case TOK.register:
            case TOK.typedef_:
            case TOK.int16:
            case TOK.inline:
            case TOK._Noreturn:
            case TOK.volatile:
            case TOK.signed:
            case TOK.auto_:
            case TOK.restrict:
            case TOK._Complex:
            case TOK._Thread_local:
            case TOK.int32:
            case TOK.__int128:
            case TOK.char_:
            case TOK.float32:
            case TOK.float64:
            case TOK.void_:
            case TOK._Bool:
            case TOK._Atomic:
                return true;

            default:
                return false;
        }
    }

    /***************************
     * Like skipParens(), but consume the tokens.
     */
    private void cparseParens()
    {
        check(TOK.leftParenthesis);
        int parens = 1;

        while (1)
        {
            switch (token.value)
            {
                case TOK.leftParenthesis:
                    ++parens;
                    break;

                case TOK.rightParenthesis:
                    --parens;
                    if (parens < 0)
                    {
                        error("extra right parenthesis");
                        return;
                    }
                    if (parens == 0)
                    {
                        nextToken();
                        return;
                    }
                    break;

                case TOK.endOfFile:
                    error("end of file found before right parenthesis");
                    return;

                default:
                    break;
            }
            nextToken();
        }
    }

    //}
    /******************************************************************************/
    /***************************** Struct & Enum Parser ***************************/
    //{

    /*************************************
     * C11 6.7.2.2
     * enum-specifier:
     *    enum identifier (opt) { enumerator-list }
     *    enum identifier (opt) { enumerator-list , }
     *    enum identifier
     *
     * enumerator-list:
     *    enumerator
     *    enumerator-list , enumerator
     *
     * enumerator:
     *    enumeration-constant
     *    enumeration-constant = constant-expression
     *
     * enumeration-constant:
     *    identifier
     *
     * Params:
     *  symbols = symbols to add enum declaration to
     * Returns:
     *  type of the enum
     */
    private AST.Type cparseEnum(ref AST.Dsymbols* symbols)
    {
        const loc = token.loc;
        nextToken();

        /* GNU Extensions
         * enum-specifier:
         *    enum gnu-attributes (opt) identifier (opt) { enumerator-list } gnu-attributes (opt)
         *    enum gnu-attributes (opt) identifier (opt) { enumerator-list , } gnu-attributes (opt)
         *    enum gnu-attributes (opt) identifier
         */
        Specifier specifier;
        specifier.packalign.setDefault();
        if (token.value == TOK.__attribute__)
            cparseGnuAttributes(specifier);

        Identifier tag;
        if (token.value == TOK.identifier)
        {
            tag = token.ident;
            nextToken();
        }

        /* clang extension: add optional base type after the identifier
         * https://en.cppreference.com/w/cpp/language/enum
         *   enum Identifier : Type
         */
        //AST.Type base = AST.Type.tint32;  // C11 6.7.2.2-4 implementation defined default base type
        AST.Type base = null;               // C23 says base type is determined by enum member values
        if (token.value == TOK.colon)
        {
            nextToken();
            base = cparseTypeName();
        }

        AST.Dsymbols* members;
        if (token.value == TOK.leftCurly)
        {
            nextToken();
            members = new AST.Dsymbols();

            if (token.value == TOK.rightCurly)  // C11 6.7.2.2-1
            {
                if (tag)
                    error("no members for `enum %s`", tag.toChars());
                else
                    error("no members for anonymous enum");
            }

            while (token.value == TOK.identifier)
            {
                auto ident = token.ident;  // enumeration-constant
                nextToken();
                auto mloc = token.loc;

                if (token.value == TOK.__attribute__)
                {
                    /* gnu-attributes can appear here, but just scan and ignore them
                     * https://gcc.gnu.org/onlinedocs/gcc/Enumerator-Attributes.html
                     */
                    Specifier specifierx;
                    specifierx.packalign.setDefault();
                    cparseGnuAttributes(specifierx);
                }

                AST.Expression value;
                if (token.value == TOK.assign)
                {
                    nextToken();
                    value = cparseConstantExp();
                    // TODO C11 6.7.2.2-2 value must fit into an int
                }

                if (token.value == TOK.__attribute__)
                {
                    /* gnu-attributes can appear here, but just scan and ignore them
                     * https://gcc.gnu.org/onlinedocs/gcc/Enumerator-Attributes.html
                     */
                    Specifier specifierx;
                    specifierx.packalign.setDefault();
                    cparseGnuAttributes(specifierx);
                }

                auto em = new AST.EnumMember(mloc, ident, value, null, 0, null, null);
                members.push(em);

                if (token.value == TOK.comma)
                {
                    nextToken();
                    continue;
                }
                break;
            }
            check(TOK.rightCurly);

            /* GNU Extensions
             * Parse the postfix gnu-attributes (opt)
             */
            if (token.value == TOK.__attribute__)
                cparseGnuAttributes(specifier);
        }
        else if (!tag)
            error("missing `identifier` after `enum`");

        /* Need semantic information to determine if this is a declaration,
         * redeclaration, or reference to existing declaration.
         * Defer to the semantic() pass with a TypeTag.
         */
        return new AST.TypeTag(loc, TOK.enum_, tag, structalign_t.init, base, members);
    }

    /*************************************
     * C11 6.7.2.1
     * Parse struct and union specifiers.
     * Parser is advanced to the tag identifier or brace.
     * struct-or-union-specifier:
     *    struct-or-union identifier (opt) { struct-declaration-list }
     *    struct-or-union identifier
     *
     * struct-or-union:
     *    struct
     *    union
     *
     * struct-declaration-list:
     *    struct-declaration
     *    struct-declaration-list struct-declaration
     *
     * Params:
     *  loc = location of `struct` or `union`
     *  structOrUnion = TOK.struct_ or TOK.union_
     *  packalign = alignment to use for struct members
     *  symbols = symbols to add struct-or-union declaration to
     * Returns:
     *  type of the struct
     */
    private AST.Type cparseStruct(Loc loc, TOK structOrUnion, structalign_t packalign, ref AST.Dsymbols* symbols)
    {
        Identifier tag;

        if (token.value == TOK.identifier)
        {
            tag = token.ident;
            nextToken();
        }

        AST.Dsymbols* members;
        if (token.value == TOK.leftCurly)
        {
            nextToken();
            members = new AST.Dsymbols();          // so `members` will be non-null even with 0 members
            while (token.value != TOK.rightCurly)
            {
                cparseStructDeclaration(members, packalign);

                if (token.value == TOK.endOfFile)
                    break;
            }
            check(TOK.rightCurly);

            if ((*members).length == 0) // C11 6.7.2.1-8
            {
                /* allow empty structs as an extension
                 *  struct-declarator-list:
                 *    struct-declarator (opt)
                 */
            }

            /* GNU Extensions
             * Parse the postfix gnu-attributes (opt)
             */
            Specifier specifier;
            if (token.value == TOK.__attribute__)
                cparseGnuAttributes(specifier);
            if (!specifier.packalign.isUnknown)
            {
                packalign.set(specifier.packalign.get());
                packalign.setPack(specifier.packalign.isPack());
                foreach (ref d; (*members)[])
                {
                    auto decls = new AST.Dsymbols(1);
                    (*decls)[0] = d;
                    d = new AST.AlignDeclaration(d.loc, specifier.packalign, decls);
                }
            }
        }
        else if (!tag)
            error("missing tag `identifier` after `%s`", Token.toChars(structOrUnion));

        // many ways and places to declare alignment
        if (packalign.isUnknown() && !this.packalign.isUnknown())
            packalign.set(this.packalign.get());

        /* Need semantic information to determine if this is a declaration,
         * redeclaration, or reference to existing declaration.
         * Defer to the semantic() pass with a TypeTag.
         */
        return new AST.TypeTag(loc, structOrUnion, tag, packalign, null, members);
    }

    /*************************************
     * C11 6.7.2.1
     * Parse a struct declaration member.
     * struct-declaration:
     *    specifier-qualifier-list struct-declarator-list (opt) ;
     *    static_assert-declaration
     *
     * struct-declarator-list:
     *    struct-declarator
     *    struct-declarator-list , struct-declarator
     *
     * struct-declarator:
     *    declarator
     *    declarator (opt) : constant-expression
     * Params:
     *    members = where to put the fields (members)
     *    packalign = alignment to use for struct members
     */
    void cparseStructDeclaration(AST.Dsymbols* members, structalign_t packalign)
    {
        //printf("cparseStructDeclaration()\n");
        if (token.value == TOK._Static_assert)
        {
            auto s = cparseStaticAssert();
            members.push(s);
            return;
        }

        Specifier specifier;
        specifier.packalign = packalign.isUnknown ? this.packalign : packalign;
        auto tspec = cparseSpecifierQualifierList(LVL.member, specifier);
        if (!tspec)
        {
            error("no type-specifier for struct member");
            tspec = AST.Type.tint32;
        }
        if (specifier.mod & MOD.xconst)
        {
            tspec = toConst(tspec);
            specifier.mod = MOD.xnone;          // 'used' it
        }

        /* If a declarator does not follow, it is unnamed
         */
        if (token.value == TOK.semicolon && tspec)
        {
            nextToken();
            auto tt = tspec.isTypeTag();
            if (!tt)
            {
                if (auto ti = tspec.isTypeIdentifier())
                {
                    error("type-specifier omitted before declaration of `%s`", ti.ident.toChars());
                }
                return; // legal but meaningless empty declaration
            }

            /* If anonymous struct declaration
             *   struct { ... members ... };
             * C11 6.7.2.1-13
             */
            if (!tt.id && tt.members)
            {
                /* members of anonymous struct are considered members of
                 * the containing struct
                 */
                auto ad = new AST.AnonDeclaration(tt.loc, tt.tok == TOK.union_, tt.members);
                auto s = applySpecifier(ad, specifier);
                members.push(s);
                return;
            }
            if (!tt.id && !tt.members)
                return; // already gave error in cparseStruct()

            /* `struct tag;` and `struct tag { ... };`
             * always result in a declaration in the current scope
             */
            // TODO: merge in specifier
            auto stag = (tt.tok == TOK.struct_)
                ? new AST.StructDeclaration(tt.loc, tt.id, false)
                : new AST.UnionDeclaration(tt.loc, tt.id);
            stag.members = tt.members;
            if (!symbols)
                symbols = new AST.Dsymbols();
            auto s = applySpecifier(stag, specifier);
            symbols.push(s);
            return;
        }

        while (1)
        {
            Identifier id;
            AST.Type dt;
            if (token.value == TOK.colon)
            {
                if (auto ti = tspec.isTypeIdentifier())
                {
                    error("type-specifier omitted before bit field declaration of `%s`", ti.ident.toChars());
                    tspec = AST.Type.tint32;
                }

                // C11 6.7.2.1-12 unnamed bit-field
                id = Identifier.generateAnonymousId("BitField");
                dt = tspec;
            }
            else
            {
                dt = cparseDeclarator(DTR.xdirect, tspec, id, specifier);
                if (!dt)
                {
                    panic();
                    nextToken();
                    break;          // error recovery
                }
            }

            AST.Expression width;
            if (token.value == TOK.colon)
            {
                // C11 6.7.2.1-10 bit-field
                nextToken();
                width = cparseConstantExp();
            }

            /* GNU Extensions
             * struct-declarator:
             *    declarator gnu-attributes (opt)
             *    declarator (opt) : constant-expression gnu-attributes (opt)
             */
            if (token.value == TOK.__attribute__)
                cparseGnuAttributes(specifier);

            if (!tspec && !specifier.scw && !specifier.mod)
                error("specifier-qualifier-list required");
            else if (width)
            {
                if (specifier.alignExps)
                    error("no alignment-specifier for bit field declaration"); // C11 6.7.5-2
                auto s = new AST.BitFieldDeclaration(width.loc, dt, id, width);
                members.push(s);
            }
            else if (id)
            {
                if (dt.ty == AST.Tvoid)
                    error("`void` has no value");

                // declare the symbol
                // Give member variables an implicit void initializer
                auto initializer = new AST.VoidInitializer(token.loc);
                AST.Dsymbol s = new AST.VarDeclaration(token.loc, dt, id, initializer, specifiersToSTC(LVL.member, specifier));
                s = applySpecifier(s, specifier);
                members.push(s);
            }

            switch (token.value)
            {
                case TOK.identifier:
                    error("missing comma");
                    goto default;

                case TOK.semicolon:
                    nextToken();
                    return;

                case TOK.comma:
                    nextToken();
                    break;

                default:
                    error("`;` or `,` expected");
                    while (token.value != TOK.semicolon && token.value != TOK.endOfFile)
                        nextToken();
                    nextToken();
                    return;
            }
        }
    }

    //}
    /******************************************************************************/
    /********************************* Lookahead Parser ***************************/
    //{

    /************************************
     * Determine if the scanner is sitting on the start of a declaration.
     * Params:
     *      t       = current token of the scanner
     *      needId  = flag with additional requirements for a declaration
     *      endtok  = ending token
     *      pt      = will be set ending token (if not null)
     * Returns:
     *      true at start of a declaration
     */
    private bool isCDeclaration(ref Token* pt)
    {
        auto t = pt;
        //printf("isCDeclaration() %s\n", t.toChars());
        if (!isDeclarationSpecifiers(t))
            return false;

        while (1)
        {
            if (t.value == TOK.semicolon)
            {
                t = peek(t);
                pt = t;
                return true;
            }
            if (!isCDeclarator(t, DTR.xdirect))
                return false;
            if (t.value == TOK.asm_)
            {
                t = peek(t);
                if (t.value != TOK.leftParenthesis || !skipParens(t, &t))
                    return false;
            }
            if (t.value == TOK.__attribute__)
            {
                t = peek(t);
                if (t.value != TOK.leftParenthesis || !skipParens(t, &t))
                    return false;
            }
            if (t.value == TOK.assign)
            {
                t = peek(t);
                if (!isInitializer(t))
                    return false;
            }
            switch (t.value)
            {
                case TOK.comma:
                    t = peek(t);
                    break;

                case TOK.semicolon:
                    t = peek(t);
                    pt = t;
                    return true;

                default:
                    return false;
            }
        }
    }

    /********************************
     * See if match for initializer.
     * Params:
     *  pt = starting token, updated to one past end of initializer if true
     * Returns:
     *  true if initializer
     */
    private bool isInitializer(ref Token* pt)
    {
        //printf("isInitializer()\n");
        auto t = pt;

        if (t.value == TOK.leftCurly)
        {
            if (!skipBraces(t))
                return false;
            pt = t;
            return true;
        }

        // skip over assignment-expression, ending before comma or semiColon or EOF
        if (!isAssignmentExpression(t))
            return false;
        pt = t;
        return true;
    }

    /********************************
     * See if match for:
     *    postfix-expression ( argument-expression-list(opt) )
     * Params:
     *  pt = starting token, updated to one past end of initializer if true
     * Returns:
     *  true if function call
     */
    private bool isFunctionCall(ref Token* pt)
    {
        //printf("isFunctionCall()\n");
        auto t = pt;

        if (!isPrimaryExpression(t))
            return false;
        if (t.value != TOK.leftParenthesis)
            return false;
        t = peek(t);
        while (1)
        {
            if (!isAssignmentExpression(t))
                return false;
            if (t.value == TOK.comma)
            {
                t = peek(t);
                continue;
            }
            if (t.value == TOK.rightParenthesis)
            {
                t = peek(t);
                break;
            }
            return false;
        }
        if (t.value != TOK.semicolon)
            return false;
        pt = t;
        return true;
    }

    /********************************
     * See if match for assignment-expression.
     * Params:
     *  pt = starting token, updated to one past end of assignment-expression if true
     * Returns:
     *  true if assignment-expression
     */
    private bool isAssignmentExpression(ref Token* pt)
    {
        auto t = pt;
        //printf("isAssignmentExpression() %s\n", t.toChars());

        /* This doesn't actually check for grammar matching an
         * assignment-expression. It just matches ( ) [ ] looking for
         * an ending token that would terminate one.
         */
        bool any;
        while (1)
        {
            switch (t.value)
            {
                case TOK.comma:
                case TOK.semicolon:
                case TOK.rightParenthesis:
                case TOK.rightBracket:
                case TOK.endOfFile:
                case TOK.endOfLine:
                    if (!any)
                        return false;
                    break;

                case TOK.leftParenthesis:
                    if (!skipParens(t, &t))
                        return false;
                    /*
                        https://issues.dlang.org/show_bug.cgi?id=22267
                        If the parser encounters the following
                            `identifier variableName = (expression);`
                        the initializer is not identified as such since the parentheses
                        cause the parser to keep walking indefinitely
                        (whereas `(1) + 1` would not be affected.).
                    */
                    any = true;
                    continue;

                case TOK.leftBracket:
                    if (!skipBrackets(t))
                        return false;
                    continue;

                case TOK.leftCurly:
                    if (!skipBraces(t))
                        return false;
                    continue;

                default:
                    any = true;   // assume token was part of an a-e
                    t = peek(t);
                    continue;
            }
            pt = t;
            return true;
        }
    }

    /********************************
     * See if match for constant-expression.
     * Params:
     *  pt = starting token, updated to one past end of constant-expression if true
     * Returns:
     *  true if constant-expression
     */
    private bool isConstantExpression(ref Token* pt)
    {
        return isAssignmentExpression(pt);
    }

    /********************************
     * See if match for declaration-specifiers.
     * No errors are diagnosed.
     * Params:
     *  pt = starting token, updated to one past end of declaration-specifiers if true
     * Returns:
     *  true if declaration-specifiers
     */
    private bool isDeclarationSpecifiers(ref Token* pt)
    {
        //printf("isDeclarationSpecifiers()\n");

        auto t = pt;

        bool seenType;
        bool any;
        while (1)
        {
            switch (t.value)
            {
                // type-specifiers
                case TOK.void_:
                case TOK.char_:
                case TOK.int16:
                case TOK.int32:
                case TOK.int64:
                case TOK.__int128:
                case TOK.float32:
                case TOK.float64:
                case TOK.signed:
                case TOK.unsigned:
                case TOK._Bool:
                //case TOK._Imaginary:
                case TOK._Complex:
                    t = peek(t);
                    seenType = true;
                    any = true;
                    continue;

                case TOK.identifier: // typedef-name
                    if (!seenType)
                    {
                        t = peek(t);
                        seenType = true;
                        any = true;
                        continue;
                    }
                    break;

                case TOK.struct_:
                case TOK.union_:
                case TOK.enum_:
                    t = peek(t);
                    if (t.value == TOK.__attribute__ ||
                        t.value == TOK.__declspec)
                    {
                        t = peek(t);
                        if (!skipParens(t, &t))
                            return false;
                    }
                    if (t.value == TOK.identifier)
                    {
                        t = peek(t);
                        if (t.value == TOK.leftCurly)
                        {
                            if (!skipBraces(t))
                                return false;
                        }
                    }
                    else if (t.value == TOK.leftCurly)
                    {
                        if (!skipBraces(t))
                            return false;
                    }
                    else
                        return false;
                    any = true;
                    continue;

                // storage-class-specifiers
                case TOK.typedef_:
                case TOK.extern_:
                case TOK.static_:
                case TOK.__thread:
                case TOK._Thread_local:
                case TOK.auto_:
                case TOK.register:

                // function-specifiers
                case TOK.inline:
                case TOK._Noreturn:

                // type-qualifiers
                case TOK.const_:
                case TOK.volatile:
                case TOK.restrict:
                case TOK.__stdcall:
                    t = peek(t);
                    any = true;
                    continue;

                case TOK._Alignas:      // alignment-specifier
                case TOK.__declspec:    // decl-specifier
                case TOK.__attribute__: // attribute-specifier
                    t = peek(t);
                    if (!skipParens(t, &t))
                        return false;
                    any = true;
                    continue;

                // either atomic-type-specifier or type_qualifier
                case TOK._Atomic:  // TODO _Atomic ( type-name )
                    t = peek(t);
                    if (t.value == TOK.leftParenthesis) // maybe atomic-type-specifier
                    {
                        auto tsave = t;
                        t = peek(t);
                        if (!isTypeName(t) || t.value != TOK.rightParenthesis)
                        {   // it's a type-qualifier
                            t = tsave;  // back up parser
                            any = true;
                            continue;
                        }
                        t = peek(t);    // move past right parenthesis of atomic-type-specifier
                    }
                    any = true;
                    continue;

                default:
                    break;
            }
            break;
        }

        if (any)
        {
            pt = t;
            return true;
        }
        return false;
    }

    /**************************************
     * See if declaration-list is present.
     * Returns:
     *    true if declaration-list is present, even an empty one
     */
    bool isDeclarationList(ref Token* pt)
    {
        auto t = pt;
        while (1)
        {
            if (t.value == TOK.leftCurly)
            {
                pt = t;
                return true;
            }
            if (!isCDeclaration(t))
                return false;
        }
    }

    /*******************************************
     * Skip braces.
     * Params:
     *      pt = enters on left brace, set to token past right bracket on true
     * Returns:
     *      true if successful
     */
    private bool skipBraces(ref Token* pt)
    {
        auto t = pt;
        if (t.value != TOK.leftCurly)
            return false;

        int braces = 0;

        while (1)
        {
            switch (t.value)
            {
                case TOK.leftCurly:
                    ++braces;
                    t = peek(t);
                    continue;

                case TOK.rightCurly:
                    --braces;
                    if (braces == 0)
                    {
                        pt = peek(t);
                        return true;
                    }
                    if (braces < 0)
                        return false;

                    t = peek(t);
                    continue;

                case TOK.endOfFile:
                    return false;

                default:
                    t = peek(t);
                    continue;
            }
        }
    }

    /*******************************************
     * Skip brackets.
     * Params:
     *      pt = enters on left bracket, set to token past right bracket on true
     * Returns:
     *      true if successful
     */
    private bool skipBrackets(ref Token* pt)
    {
        auto t = pt;
        if (t.value != TOK.leftBracket)
            return false;

        int brackets = 0;

        while (1)
        {
            switch (t.value)
            {
                case TOK.leftBracket:
                    ++brackets;
                    t = peek(t);
                    continue;

                case TOK.rightBracket:
                    --brackets;
                    if (brackets == 0)
                    {
                        pt = peek(t);
                        return true;
                    }
                    if (brackets < 0)
                        return false;

                    t = peek(t);
                    continue;

                case TOK.endOfFile:
                    return false;

                default:
                    t = peek(t);
                    continue;
            }
        }
    }

    /*********************************
     * Check to see if tokens starting with *pt form a declarator.
     * Params:
     *  pt = pointer to starting token, updated to point past declarator if true is returned
     *  declarator = declarator kind
     * Returns:
     *  true if it does
     */
    private bool isCDeclarator(ref Token* pt, DTR declarator)
    {
        //printf("isCDeclarator()\n");
        auto t = pt;
        while (1)
        {
            if (t.value == TOK.mul)     // pointer
            {
                t = peek(t);
                if (!isTypeQualifierList(t))
                    return false;
            }
            else
                break;
        }

        if (t.value == TOK.identifier)
        {
            if (declarator == DTR.xabstract)
                return false;
            t = peek(t);
        }
        else if (t.value == TOK.leftParenthesis)
        {
            t = peek(t);
            if (t.value == TOK.__stdcall)
                t = peek(t);
            if (!isCDeclarator(t, declarator))
                return false;
            if (t.value != TOK.rightParenthesis)
                return false;
            t = peek(t);
        }
        else if (declarator == DTR.xdirect)
        {
            return false;
        }

        while (1)
        {
            if (t.value == TOK.leftBracket)
            {
                if (!skipBrackets(t))
                    return false;
            }
            else if (t.value == TOK.leftParenthesis)
            {
                if (!skipParens(t, &t))
                    return false;
            }
            else
                break;
        }
        pt = t;
        return true;
    }

    /***************************
     * Is this the start of a type-qualifier-list?
     * (Can be empty.)
     * Params:
     *  pt = first token; updated with past end of type-qualifier-list if true
     * Returns:
     *  true if start of type-qualifier-list
     */
    private bool isTypeQualifierList(ref Token* pt)
    {
        auto t = pt;
        while (1)
        {
            switch (t.value)
            {
                case TOK.const_:
                case TOK.restrict:
                case TOK.volatile:
                case TOK._Atomic:
                case TOK.__stdcall:
                    t = peek(t);
                    continue;

                default:
                    break;
            }
            break;
        }
        pt = t;
        return true;
    }

    /***************************
     * Is this the start of a type-name?
     * Params:
     *  pt = first token; updated with past end of type-name if true
     * Returns:
     *  true if start of type-name
     */
    private bool isTypeName(ref Token* pt)
    {
        auto t = pt;
        //printf("isTypeName() %s\n", t.toChars());
        if (!isSpecifierQualifierList(t))
            return false;
        if (!isCDeclarator(t, DTR.xabstract))
            return false;
        if (t.value != TOK.rightParenthesis)
            return false;
        pt = t;
        return true;
    }

    /***************************
     * Is this the start of a specifier-qualifier-list?
     * Params:
     *  pt = first token; updated with past end of specifier-qualifier-list if true
     * Returns:
     *  true if start of specifier-qualifier-list
     */
    private bool isSpecifierQualifierList(ref Token* pt)
    {
        auto t = pt;
        bool result;
        while (1)
        {
            switch (t.value)
            {
                // Type Qualifiers
                case TOK.const_:
                case TOK.restrict:
                case TOK.volatile:
                case TOK.__stdcall:

                // Type Specifiers
                case TOK.char_:
                case TOK.signed:
                case TOK.unsigned:
                case TOK.int16:
                case TOK.int32:
                case TOK.int64:
                case TOK.__int128:
                case TOK.float32:
                case TOK.float64:
                case TOK.void_:
                case TOK._Bool:
                //case TOK._Imaginary: // ? missing in Spec
                case TOK._Complex:
                    t = peek(t);
                    break;

                case TOK.identifier:
                    // Use typedef table to disambiguate
                    if (isTypedef(t.ident))
                    {
                        t = peek(t);
                        break;
                    }
                    else
                    {
                        return false;
                    }

                // struct-or-union-specifier
                // enum-specifier
                case TOK.struct_:
                case TOK.union_:
                case TOK.enum_:
                    t = peek(t);
                    if (t.value == TOK.identifier)
                    {
                        t = peek(t);
                        if (t.value == TOK.leftCurly)
                        {
                            if (!skipBraces(t))
                                return false;
                        }
                    }
                    else if (t.value == TOK.leftCurly)
                    {
                        if (!skipBraces(t))
                            return false;
                    }
                    else
                        return false;
                    break;

                // atomic-type-specifier
                case TOK._Atomic:
                case TOK.typeof_:
                case TOK.__attribute__:
                    t = peek(t);
                    if (t.value != TOK.leftParenthesis ||
                        !skipParens(t, &t))
                        return false;
                    break;

                default:
                    if (result)
                        pt = t;
                    return result;
            }
            result = true;
        }
    }

    /************************************
     * Looking at the leading left parenthesis, and determine if it is
     * either of the following:
     *    ( type-name ) cast-expression
     *    ( type-name ) { initializer-list }
     * as opposed to:
     *    ( expression )
     * Params:
     *    pt = starting token, updated to one past end of constant-expression if true
     *    afterParenType = true if already seen `( type-name )`
     * Returns:
     *    true if matches ( type-name ) ...
     */
    private bool isCastExpression(ref Token* pt, bool afterParenType = false)
    {
        enum log = false;
        if (log) printf("isCastExpression(tk: `%s`, afterParenType: %d)\n", token.toChars(pt.value), afterParenType);
        auto t = pt;
        switch (t.value)
        {
            case TOK.leftParenthesis:
                auto tk = peek(t);  // move past left parenthesis
                if (!isTypeName(tk) || tk.value != TOK.rightParenthesis)
                {
                    if (afterParenType)
                        goto default; // could be ( type-name ) ( unary-expression )
                    return false;
                }
                tk = peek(tk);  // move past right parenthesis

                if (tk.value == TOK.leftCurly)
                {
                    // ( type-name ) { initializer-list }
                    if (!isInitializer(tk))
                    {
                        return false;
                    }
                    t = tk;
                    break;
                }

                if (tk.value == TOK.leftParenthesis && peek(tk).value == TOK.rightParenthesis)
                {
                    return false;    // (type-name)() is not a cast (it might be a function call)
                }

                if (!isCastExpression(tk, true))
                {
                    if (afterParenType) // could be ( type-name ) ( unary-expression )
                        goto default;   // where unary-expression also matched type-name
                    return true;
                }
                // ( type-name ) cast-expression
                t = tk;
                break;

            default:
                if (!afterParenType || !isUnaryExpression(t, afterParenType))
                {
                    return false;
                }
                // if we've already seen ( type-name ), then this is a cast
                break;
        }
        pt = t;
        if (log) printf("isCastExpression true\n");
        return true;
    }

    /********************************
     * See if match for unary-expression.
     * Params:
     *    pt = starting token, updated to one past end of constant-expression if true
     *    afterParenType = true if already seen ( type-name ) of a cast-expression
     * Returns:
     *    true if unary-expression
     */
    private bool isUnaryExpression(ref Token* pt, bool afterParenType = false)
    {
        auto t = pt;
        switch (t.value)
        {
            case TOK.plusPlus:
            case TOK.minusMinus:
                t = peek(t);
                if (!isUnaryExpression(t, afterParenType))
                    return false;
                break;

            case TOK.and:
            case TOK.mul:
            case TOK.min:
            case TOK.add:
            case TOK.not:
            case TOK.tilde:
                t = peek(t);
                if (!isCastExpression(t, afterParenType))
                    return false;
                break;

            case TOK.sizeof_:
                t = peek(t);
                if (t.value == TOK.leftParenthesis)
                {
                    auto tk = peek(t);
                    if (isTypeName(tk))
                    {
                        if (tk.value != TOK.rightParenthesis)
                            return false;
                        t = peek(tk);
                        break;
                    }
                }
                if (!isUnaryExpression(t, afterParenType))
                    return false;
                break;

            case TOK._Alignof:
                t = peek(t);
                if (t.value != TOK.leftParenthesis)
                    return false;
                t = peek(t);
                if (!isTypeName(t) || t.value != TOK.rightParenthesis)
                    return false;
                break;

            default:
                // Compound literals are handled by cast and sizeof expressions,
                // so be content with just seeing a primary expression.
                if (!isPrimaryExpression(t))
                    return false;
                break;
        }
        pt = t;
        return true;
    }

    /********************************
     * See if match for primary-expression.
     * Params:
     *    pt = starting token, updated to one past end of constant-expression if true
     * Returns:
     *    true if primary-expression
     */
    private bool isPrimaryExpression(ref Token* pt)
    {
        auto t = pt;
        switch (t.value)
        {
            case TOK.identifier:
            case TOK.charLiteral:
            case TOK.wcharLiteral:
            case TOK.dcharLiteral:
            case TOK.int32Literal:
            case TOK.uns32Literal:
            case TOK.int64Literal:
            case TOK.uns64Literal:
            case TOK.float32Literal:
            case TOK.float64Literal:
            case TOK.float80Literal:
            case TOK.imaginary32Literal:
            case TOK.imaginary64Literal:
            case TOK.imaginary80Literal:
            case TOK.string_:
                t = peek(t);
                break;

            case TOK.leftParenthesis:
                // ( expression )
                if (!skipParens(t, &t))
                    return false;
                break;

            case TOK._Generic:
                t = peek(t);
                if (!skipParens(t, &t))
                    return false;
                break;

            default:
                return false;
        }
        pt = t;
        return true;
    }

    //}
    /******************************************************************************/
    /********************************* More ***************************************/
    //{

    /**************
     * Declaration context
     */
    enum LVL
    {
        global    = 1,    /// global
        parameter = 2,    /// function parameter (declarations for function identifier-list)
        prototype = 4,    /// function prototype
        local     = 8,    /// local
        member    = 0x10, /// struct member
    }

    /// Types of declarator to parse
    enum DTR
    {
        xdirect    = 1, /// C11 6.7.6 direct-declarator
        xabstract  = 2, /// C11 6.7.7 abstract-declarator
        xparameter = 3, /// parameter declarator may be either direct or abstract
    }

    /// C11 6.7.1 Storage-class specifiers
    enum SCW : uint
    {
        xnone      = 0,
        xtypedef   = 1,
        xextern    = 2,
        xstatic    = 4,
        x_Thread_local = 8,
        xauto      = 0x10,
        xregister  = 0x20,
        // C11 6.7.4 Function specifiers
        xinline    = 0x40,
        x_Noreturn = 0x80,

        xnoinline  = 0x100,
    }

    /// C11 6.7.3 Type qualifiers
    enum MOD : uint
    {
        xnone     = 0,
        xconst    = 1,
        xvolatile = 2,
        xrestrict = 4,
        x_Atomic  = 8,
        x__stdcall = 0x10, // Windows linkage extension
    }

    /**********************************
     * Aggregate for all the various specifiers
     */
    struct Specifier
    {
        bool noreturn;  /// noreturn attribute
        bool naked;     /// naked attribute
        bool _nothrow;  /// nothrow attribute
        bool _pure;     /// pure attribute
        bool dllimport; /// dllimport attribute
        bool dllexport; /// dllexport attribute
        bool _deprecated;       /// deprecated attribute
        AST.Expression depMsg;  /// deprecated message
        uint vector_size;       /// positive power of 2 multipe of base type size

        SCW scw;        /// storage-class specifiers
        MOD mod;        /// type qualifiers
        AST.Expressions*  alignExps;  /// alignment
        structalign_t packalign;  /// #pragma pack alignment value
    }

    /***********************
     * Convert from C specifiers to D storage class
     * Params:
     *  level = declaration context
     *  specifier = specifiers, context, etc.
     * Returns:
     *  corresponding D storage class
     */
    StorageClass specifiersToSTC(LVL level, const ref Specifier specifier)
    {
        StorageClass stc;
        if (specifier.scw & SCW.x_Thread_local)
        {
            if (level == LVL.global)
            {
                if (specifier.scw & SCW.xextern)
                    stc = AST.STC.extern_;
                else if (specifier.scw & SCW.xstatic)
                    stc = AST.STC.static_;
            }
            else if (level == LVL.local)
            {
                if (specifier.scw & SCW.xextern)
                    stc = AST.STC.extern_;
                else if (specifier.scw & SCW.xstatic)
                    stc = AST.STC.static_;
            }
            else if (level == LVL.member)
            {
                if (specifier.scw & SCW.xextern)
                    stc = AST.STC.extern_;
                else if (specifier.scw & SCW.xstatic)
                    stc = AST.STC.static_;
            }
        }
        else
        {
            if (level == LVL.global)
            {
                if (specifier.scw & SCW.xextern)
                    stc = AST.STC.extern_ | AST.STC.gshared;
                else if (specifier.scw & SCW.xstatic)
                    stc = AST.STC.gshared | AST.STC.static_;
                else
                    stc = AST.STC.gshared;
            }
            else if (level == LVL.local)
            {
                if (specifier.scw & SCW.xextern)
                    stc = AST.STC.extern_ | AST.STC.gshared;
                else if (specifier.scw & SCW.xstatic)
                    stc = AST.STC.gshared;
                else if (specifier.scw & SCW.xregister)
                    stc = AST.STC.register;
            }
            else if (level == LVL.parameter)
            {
                if (specifier.scw & SCW.xregister)
                    stc = AST.STC.register | AST.STC.parameter;
                else
                    stc = AST.STC.parameter;
            }
            else if (level == LVL.member)
            {
                if (specifier.scw & SCW.xextern)
                    stc = AST.STC.extern_ | AST.STC.gshared;
                else if (specifier.scw & SCW.xstatic)
                    stc = AST.STC.gshared;
            }
        }
        if (specifier._deprecated && !specifier.depMsg)
            stc |= AST.STC.deprecated_;
        return stc;
    }

    /***********************
     * Add attributes from Specifier to function
     * Params:
     *  fd = function to apply them to
     *  specifier = specifiers
     */
    void specifiersToFuncDeclaration(AST.FuncDeclaration fd, const ref Specifier specifier)
    {
        fd.isNaked = specifier.naked;
        fd.dllImport = specifier.dllimport;
        fd.dllExport = specifier.dllexport;

        if (specifier.scw & SCW.xnoinline)
            fd.inlining = PINLINE.never;
        else if (specifier.scw & SCW.xinline)
            fd.inlining = PINLINE.always;
    }

    /***********************
     * Add attributes from Specifier to variable
     * Params:
     *  vd = function to apply them to
     *  specifier = specifiers
     */
    void specifiersToVarDeclaration(AST.VarDeclaration vd, const ref Specifier specifier)
    {
        vd.dllImport = specifier.dllimport;
        vd.dllExport = specifier.dllexport;
    }

    /***********************
     * Return suitable signed integer type for the given size
     * Params:
     *  size = size of type
     * Returns:
     *  corresponding signed D integer type
     */
    private AST.Type integerTypeForSize(ubyte size)
    {
        if (size <= 1)
            return AST.Type.tint8;
        if (size <= 2)
            return AST.Type.tint16;
        if (size <= 4)
            return AST.Type.tint32;
        if (size <= 8)
            return AST.Type.tint64;
        if (size == 16)
        {
            error("__int128 not supported");
            return AST.Type.terror;
        }
        error("unsupported integer type");
        return AST.Type.terror;
    }

    /***********************
     * Return suitable unsigned integer type for the given size
     * Params:
     *  size = size of type
     * Returns:
     *  corresponding unsigned D integer type
     */
    private AST.Type unsignedTypeForSize(ubyte size)
    {
        if (size <= 1)
            return AST.Type.tuns8;
        if (size <= 2)
            return AST.Type.tuns16;
        if (size <= 4)
            return AST.Type.tuns32;
        if (size <= 8)
            return AST.Type.tuns64;
        if (size == 16)
        {
            error("unsigned __int128 not supported");
            return AST.Type.terror;
        }
        error("unsupported integer type");
        return AST.Type.terror;
    }

    /***********************
     * Return suitable D float type for C `long double`
     * Params:
     *  flags = kind of float to return (real, imaginary, complex).
     * Returns:
     *  corresponding D type
     */
    private AST.Type realType(RTFlags flags)
    {
        if (long_doublesize == AST.Type.tfloat80.size())
        {
            // On GDC and LDC, D `real` types map to C `long double`, so never
            // return a double type when real.sizeof == double.sizeof.
            final switch (flags)
            {
                case RTFlags.realfloat: return AST.Type.tfloat80;
                case RTFlags.imaginary: return AST.Type.timaginary80;
                case RTFlags.complex:   return AST.Type.tcomplex80;
            }
        }
        else
        {
            final switch (flags)
            {
                case RTFlags.realfloat: return long_doublesize == 8 ? AST.Type.tfloat64 : AST.Type.tfloat80;
                case RTFlags.imaginary: return long_doublesize == 8 ? AST.Type.timaginary64 : AST.Type.timaginary80;
                case RTFlags.complex:   return long_doublesize == 8 ? AST.Type.tcomplex64 : AST.Type.tcomplex80;
            }
        }
    }

    /**************
     * Flags for realType
     */
    private enum RTFlags
    {
        realfloat,
        imaginary,
        complex,
    }

    /********************
     * C11 6.4.2.2 Create declaration to predefine __func__
     *    `static const char __func__[] = " function-name ";`
     * Params:
     *    loc = location for this declaration
     *    id = identifier of function
     * Returns:
     *    statement representing the declaration of __func__
     */
    private AST.Statement createFuncName(Loc loc, Identifier id)
    {
        const fn = id.toString();  // function-name
        auto efn = new AST.StringExp(loc, fn, fn.length, 1, 'c');
        auto ifn = new AST.ExpInitializer(loc, efn);
        auto lenfn = new AST.IntegerExp(loc, fn.length + 1, AST.Type.tuns32); // +1 for terminating 0
        auto tfn = new AST.TypeSArray(AST.Type.tchar, lenfn);
        efn.type = tfn.makeImmutable();
        efn.committed = true;
        auto sfn = new AST.VarDeclaration(loc, tfn, Id.__func__, ifn, STC.gshared | STC.immutable_);
        auto e = new AST.DeclarationExp(loc, sfn);
        return new AST.ExpStatement(loc, e);
    }

    /************************
     * After encountering an error, scan forward until a right brace or ; is found
     * or the end of the file.
     */
    void panic()
    {
        while (token.value != TOK.rightCurly && token.value != TOK.semicolon && token.value != TOK.endOfFile)
            nextToken();
    }

    /**************************
     * Apply `const` to a type.
     * Params:
     *    t = type to add const to
     * Returns:
     *    resulting type
     */
    private AST.Type toConst(AST.Type t)
    {
        // `const` is always applied to the return type, not the
        // type function itself.
        if (auto tf = t.isTypeFunction())
            tf.next = tf.next.addSTC(STC.const_);
        else if (auto tt = t.isTypeTag())
            tt.mod |= MODFlags.const_;
        else
        {
            /* Ignore const if the result would be const pointer to mutable
             */
            auto tn = t.nextOf();
            if (!tn || tn.isConst())
                t = t.addSTC(STC.const_);
        }
        return t;
    }

    /***************************
     * Apply specifier to a Dsymbol.
     * Params:
     *  s = Dsymbol
     *  specifier = specifiers to apply
     * Returns:
     *  Dsymbol with specifiers applied
     */
    private AST.Dsymbol applySpecifier(AST.Dsymbol s, ref Specifier specifier)
    {
        //printf("applySpecifier() %s\n", s.toChars());
        if (specifier._deprecated)
        {
            if (specifier.depMsg)
            {
                // Wrap declaration in a DeprecatedDeclaration
                auto decls = new AST.Dsymbols(1);
                (*decls)[0] = s;
                s = new AST.DeprecatedDeclaration(specifier.depMsg, decls);
            }
        }

        if (specifier.alignExps)
        {
            //printf("  applying _Alignas %s, packalign %d\n", (*specifier.alignExps)[0].toChars(), cast(int)specifier.packalign);
            // Wrap declaration in an AlignDeclaration
            auto decls = new AST.Dsymbols(1);
            (*decls)[0] = s;
            s = new AST.AlignDeclaration(s.loc, specifier.alignExps, decls);
        }
        else if (!specifier.packalign.isDefault() && !specifier.packalign.isUnknown())
        {
            //printf("  applying packalign %d\n", cast(int)specifier.packalign);
            // Wrap #pragma pack in an AlignDeclaration
            auto decls = new AST.Dsymbols(1);
            (*decls)[0] = s;
            s = new AST.AlignDeclaration(s.loc, specifier.packalign, decls);
        }
        return s;
    }

    //}

    /******************************************************************************/
    /************************** typedefTab symbol table ***************************/
    //{

    /********************************
     * Determines if type t is a function type.
     * Params:
     *  t = type to test
     * Returns:
     *  true if it represents a function
     */
    bool isFunctionTypedef(AST.Type t)
    {
        //printf("isFunctionTypedef() %s\n", t.toChars());
        if (t.isTypeFunction())
            return true;
        if (auto tid = t.isTypeIdentifier())
        {
            auto pt = lookupTypedef(tid.ident);
            if (pt && *pt)
            {
                return (*pt).isTypeFunction() !is null;
            }
        }
        return false;
    }

    /********************************
     * Determine if `id` is a symbol for a Typedef.
     * Params:
     *  id = possible typedef
     * Returns:
     *  true if id is a Type
     */
    bool isTypedef(Identifier id)
    {
        auto pt = lookupTypedef(id);
        return (pt && *pt);
    }

    /*******************************
     * Add `id` to typedefTab[], but only if it will mask an existing typedef.
     * Params: id = identifier for non-typedef symbol
     */
    void insertIdToTypedefTab(Identifier id)
    {
        //printf("insertIdToTypedefTab(id: %s) level %d\n", id.toChars(), cast(int)typedefTab.length - 1);
        if (isTypedef(id))  // if existing typedef
        {
            /* Add id as null, so we can later distinguish it from a non-null typedef
             */
            auto tab = cast(void*[void*])(typedefTab[$ - 1]);
            tab[cast(void*)id] = cast(void*)null;
        }
    }

    /*******************************
     * Add `id` to typedefTab[]
     * Params:
     *  id = identifier for typedef symbol
     *  t = type of the typedef symbol
     */
    void insertTypedefToTypedefTab(Identifier id, AST.Type t)
    {
        //printf("insertTypedefToTypedefTab(id: %s, t: %s) level %d\n", id.toChars(), t ? t.toChars() : "null".ptr, cast(int)typedefTab.length - 1);
        if (auto tid = t.isTypeIdentifier())
        {
            // Try to resolve the TypeIdentifier to its type
            auto pt = lookupTypedef(tid.ident);
            if (pt && *pt)
                t = *pt;
        }
        auto tab = cast(void*[void*])(typedefTab[$ - 1]);
        tab[cast(void*)id] = cast(void*)t;
        typedefTab[$ - 1] = cast(void*)tab;
    }

    /*********************************
     * Lookup id in typedefTab[].
     * Returns:
     *  if not found, then null.
     *  if found, then Type*. Deferencing it will yield null if it is not
     *  a typedef, and a type if it is a typedef.
     */
    AST.Type* lookupTypedef(Identifier id)
    {
        foreach_reverse (tab; typedefTab[])
        {
            if (auto pt = cast(void*)id in cast(void*[void*])tab)
            {
                return cast(AST.Type*)pt;
            }
        }
        return null; // not found
    }

    //}

    /******************************************************************************/
    /********************************* Directive Parser ***************************/
    //{

    override bool parseSpecialTokenSequence()
    {
        Token n;
        scan(&n);
        if (n.value == TOK.int32Literal)
        {
            poundLine(n, true);
            return true;
        }
        if (n.value == TOK.identifier)
        {
            if (n.ident == Id.line)
            {
                poundLine(n, false);
                return true;
            }
            else if (defines && (n.ident == Id.define || n.ident == Id.undef))
            {
                /* Append this line to `defines`.
                 * Not canonicalizing it - assume it already is
                 */
                defines.writeByte('#');
                defines.writestring(n.ident.toString());
                skipToNextLine(defines);
                defines.writeByte(0);           // each #define line is 0 terminated
                return true;
            }
            else if (n.ident == Id.__pragma)
            {
                pragmaDirective(scanloc);
                return true;
            }
            else if (n.ident == Id.ident) // #ident "string"
            {
                scan(&n);
                if (n.value == TOK.string_ && n.ptr[0] == '"' && n.postfix == 0)
                {
                    /* gcc inserts string into the .comment section in the object file.
                     * Just ignore it for now, but can support it later by writing
                     * the string to obj_exestr()
                     */
                    //auto comment = n.ustring;

                    scan(&n);
                    if (n.value == TOK.endOfFile || n.value == TOK.endOfLine)
                        return true;
                }
                error("\"string\" expected after `#ident`");
                return false;
            }
        }
        if (n.ident != Id.undef)
            error("C preprocessor directive `#%s` is not supported", n.toChars());
        return false;
    }

    /*********************************************
     * VC __pragma
     * https://docs.microsoft.com/en-us/cpp/preprocessor/pragma-directives-and-the-pragma-keyword?view=msvc-170
     * Scanner is on the `__pragma`
     * Params:
     *  startloc = location to use for error messages
     */
    private void uupragmaDirective(const ref Loc startloc)
    {
        const loc = startloc;
        nextToken();    // move past __pragma
        if (token.value != TOK.leftParenthesis)
        {
            error(loc, "left parenthesis expected to follow `__pragma` instead of `%s`", token.toChars());
            nextToken();
            return;
        }
        nextToken();

        if (token.value == TOK.identifier)
        {
            if (token.ident == Id.pack)
                pragmaPack(startloc, false);
            else
            {
                nextToken();
                if (token.value == TOK.leftParenthesis)
                    cparseParens();
            }

        }
        else if (token.value == TOK.endOfFile)
        {
        }
        else if (token.value == TOK.rightParenthesis)
        {
        }
        else
            error(loc, "unrecognized `__pragma(%s)`", token.toChars());

        if (token.value != TOK.rightParenthesis)
        {
            error(loc, "right parenthesis expected to close `__pragma(...)` instead of `%s`", token.toChars());
            return;
        }
        nextToken();
    }

    /*********************************************
     * C11 6.10.6 Pragma directive
     * # pragma pp-tokens(opt) new-line
     * The C preprocessor sometimes leaves pragma directives in
     * the preprocessed output. Ignore them.
     * Upon return, p is at start of next line.
     */
    private void pragmaDirective(const ref Loc loc)
    {
        Token n;
        scan(&n);
        if (n.value == TOK.identifier && n.ident == Id.pack)
            return pragmaPack(loc, true);
        if (n.value == TOK.identifier && n.ident == Id.attribute)
            return pragmaAttribute(loc);
        if (n.value != TOK.endOfLine)
            skipToNextLine();
    }

    /*********
     * # pragma pack
     * https://gcc.gnu.org/onlinedocs/gcc-4.4.4/gcc/Structure_002dPacking-Pragmas.html
     * https://docs.microsoft.com/en-us/cpp/preprocessor/pack
     * Scanner is on the `pack`
     * Params:
     *  startloc = location to use for error messages
     *  useScan = use scan() to retrieve next token, instead of nextToken()
     */
    private void pragmaPack(const ref Loc startloc, bool useScan)
    {
        const loc = startloc;

        /* Pull tokens from scan() or nextToken()
         */
        void scan(Token* t)
        {
            if (useScan)
            {
                Lexer.scan(t);
            }
            else
            {
                nextToken();
                *t = token;
            }
        }

        Token n;
        scan(&n);
        if (n.value != TOK.leftParenthesis)
        {
            error(loc, "left parenthesis expected to follow `#pragma pack`");
            if (n.value != TOK.endOfLine)
                skipToNextLine();
            return;
        }

        void closingParen()
        {
            if (n.value != TOK.rightParenthesis)
            {
                error(loc, "right parenthesis expected to close `#pragma pack(`");
            }
            if (n.value != TOK.endOfLine)
                skipToNextLine();
        }

        void setPackAlign(ref const Token t)
        {
            const n = t.unsvalue;
            if (n < 1 || n & (n - 1) || ushort.max < n)
                error(loc, "pack must be an integer positive power of 2, not 0x%llx", cast(ulong)n);
            packalign.set(cast(uint)n);
            packalign.setPack(true);
        }

        scan(&n);

        if (!records)
        {
            records = new Array!Identifier;
            packs = new Array!structalign_t;
        }

        /* # pragma pack ( show )
         */
        if (n.value == TOK.identifier && n.ident == Id.show)
        {
            if (packalign.isDefault())
                eSink.warning(startloc, "current pack attribute is default");
            else
                eSink.warning(startloc, "current pack attribute is %d", packalign.get());
            scan(&n);
            return closingParen();
        }
        /* # pragma pack ( push )
         * # pragma pack ( push , identifier )
         * # pragma pack ( push , integer )
         * # pragma pack ( push , identifier , integer )
         */
        if (n.value == TOK.identifier && n.ident == Id.push)
        {
            scan(&n);
            Identifier record = null;
            if (n.value == TOK.comma)
            {
                scan(&n);
                if (n.value == TOK.identifier)
                {
                    record = n.ident;
                    scan(&n);
                    if (n.value == TOK.comma)
                    {
                        scan(&n);
                        if (n.value == TOK.int32Literal)
                        {
                            setPackAlign(n);
                            scan(&n);
                        }
                        else
                            error(loc, "alignment value expected, not `%s`", n.toChars());
                    }
                }
                else if (n.value == TOK.int32Literal)
                {
                    setPackAlign(n);
                    scan(&n);
                }
                else
                    error(loc, "alignment value expected, not `%s`", n.toChars());
            }
            this.records.push(record);
            this.packs.push(packalign);
            return closingParen();
        }
        /* # pragma pack ( pop )
         * # pragma pack ( pop PopList )
         * PopList :
         *    , IdentifierOrInteger
         *    , IdentifierOrInteger PopList
         * IdentifierOrInteger:
         *      identifier
         *      integer
         */
        if (n.value == TOK.identifier && n.ident == Id.pop)
        {
            scan(&n);
            size_t len = this.records.length;
            if (n.value == TOK.rightParenthesis) // #pragma pack ( pop )
            {
                if (len == 0)   // nothing to pop
                    return closingParen();

                this.records.setDim(len - 1);
                this.packs.setDim(len - 1);
                if (len == 1)   // stack is now empty
                    packalign.setDefault();
                else
                    packalign = (*this.packs)[len - 2];
                return closingParen();
            }
            while (n.value == TOK.comma)        // #pragma pack ( pop ,
            {
                scan(&n);
                if (n.value == TOK.identifier)
                {
                    /* pragma pack(pop, identifier
                     * Pop until identifier is found, pop that one too, and set
                     * alignment to the new top of the stack.
                     * If identifier is not found, do nothing.
                     */
                    for ( ; len; --len)
                    {
                        if ((*this.records)[len - 1] == n.ident)
                        {
                            this.records.setDim(len - 1);
                            this.packs.setDim(len - 1);
                            if (len > 1)
                                packalign = (*this.packs)[len - 2];
                            else
                                packalign.setDefault(); // stack empty, use default
                            break;
                        }
                    }
                    scan(&n);
                }
                else if (n.value == TOK.int32Literal)
                {
                    setPackAlign(n);
                    scan(&n);
                }
                else
                {
                    error(loc, "identifier or alignment value expected following `#pragma pack(pop,` not `%s`", n.toChars());
                    scan(&n);
                }
            }
            return closingParen();
        }
        /* # pragma pack ( integer )
         * Sets alignment to integer
         */
        if (n.value == TOK.int32Literal)
        {
            setPackAlign(n);
            scan(&n);
            return closingParen();
        }
        /* # pragma pack ( )
         * Sets alignment to default
         */
        if (n.value == TOK.rightParenthesis)
        {
            packalign.setDefault();
            return closingParen();
        }

        error(loc, "unrecognized `#pragma pack(%s)`", n.toChars());
        if (n.value != TOK.endOfLine)
            skipToNextLine();
    }

    /*********
     * # pragma attribute(...)
     * Sets default storage classes
     * Params:
     *  startloc = location to use for error messages
     */
    private void pragmaAttribute(const ref Loc startloc)
    {
        const loc = startloc;

        if (!defaultStorageClassesStack)
        {
            defaultStorageClassesStack = new Array!STC;
        }

        Token n;
        Lexer.scan(&n);
        if (n.value != TOK.leftParenthesis)
        {
            error(loc, "left parenthesis expected to follow `#pragma attribute`");
            if (n.value != TOK.endOfLine)
                skipToNextLine();
            return;
        }

        void closingParen()
        {
            if (n.value != TOK.rightParenthesis)
            {
                error(loc, "right parenthesis expected to close `#pragma attribute(`");
            }
            if (n.value != TOK.endOfLine)
                skipToNextLine();
        }

        Lexer.scan(&n);

        /* # pragma attribute (push, ...)
         */
        if (n.value == TOK.identifier && n.ident == Id.push)
        {
            Lexer.scan(&n);
            if (n.value != TOK.comma)
            {
                error(loc, "comma expected to follow `#pragma attribute(push`");
                if (n.value != TOK.endOfLine)
                    skipToNextLine();
                return;
            }

            while (1)
            {
                Lexer.scan(&n);
                if (n.value == TOK.endOfLine)
                {
                    error(loc, "right parenthesis expected to close `#pragma attribute(push, `");
                    break;
                }

                if (n.value == TOK.rightParenthesis)
                    break;

                if (n.value == TOK.identifier)
                {
                    if (n.ident == Id._nothrow)
                        defaultStorageClasses |= STC.nothrow_;
                    else if (n.ident == Id.nogc)
                        defaultStorageClasses |= STC.nogc;
                    else if (n.ident == Id._pure)
                        defaultStorageClasses |= STC.pure_;
                    // Ignore unknown identifiers
                }
                else
                {
                    error(loc, "unrecognized `#pragma attribute(push, %s)`", n.toChars());
                    break;
                }

                Lexer.scan(&n);

                if (n.value == TOK.rightParenthesis)
                    break;

                if (n.value != TOK.comma)
                {
                    error(loc, "unrecognized `#pragma attribute(push, %s)`", n.toChars());
                    break;
                }
            }

            this.defaultStorageClassesStack.push(defaultStorageClasses);

            return closingParen();
        }

        /* # pragma attribute(pop)
         */
        if (n.value == TOK.identifier && n.ident == Id.pop)
        {
            scan(&n);
            size_t len = this.defaultStorageClassesStack.length;

            if (len)
            {
                this.defaultStorageClassesStack.setDim(len - 1);
                if (len == 1)   // stack is now empty
                    defaultStorageClasses = STC.init;
                else
                    defaultStorageClasses = (*this.defaultStorageClassesStack)[len - 2];
            }

            return closingParen();
        }

        error(loc, "unrecognized `#pragma attribute(%s)`", n.toChars());
        if (n.value != TOK.endOfLine)
            skipToNextLine();
    }

    //}

    /******************************************************************************/
    /********************************* #define Parser *****************************/
    //{

    /**
     * Go through the #define's in the defines buffer and see what we can convert
     * to Dsymbols, which are then appended to symbols[]
     */
    void addDefines()
    {
        if (!defines || defines.length < 10)  // minimum length of a #define line
            return;
        OutBuffer* buf = defines;
        defines = null;                 // prevent skipToNextLine() and parseSpecialTokenSequence()
                                        // from appending to slice[]
        const length = buf.length;
        buf.writeByte(0);
        auto slice = buf.peekChars()[0 .. length];
        auto scanlocSave = scanloc;
        resetDefineLines(slice);        // reset lexer
        auto save = eSink;
        auto eLatch = new ErrorSinkLatch();
        eSink = eLatch;

        const(char)* endp = &slice[length - 7];

        AST.Dsymbols newSymbols;

        size_t[void*] defineTab;    // hash table of #define's turned into Symbol's
                                    // indexed by Identifier, returns index into newSymbols[]
                                    // The memory for this is leaked

        void addSym(AST.Dsymbol s)
        {
            //printf("addSym() %s\n", s.toChars());
            if (auto v = s.isVarDeclaration())
                v.isCmacro(true);       // mark it as coming from a C #define
            /* If it's already defined, replace the earlier
             * definition
             */
            if (size_t* pd = cast(void*)s.ident in defineTab)
            {
                //printf("replacing %s\n", s.toChars());
                newSymbols[*pd] = s;
                return;
            }
            defineTab[cast(void*)s.ident] = newSymbols.length;
            newSymbols.push(s);
        }

        void removeSym(Identifier ident)
        {
            //printf("removeSym() %s\n", ident.toChars());
            if (size_t* pd = cast(void*)ident in defineTab)
            {
                //printf("removing %s\n", ident.toChars());
                newSymbols[*pd] = null;
            }
        }

        while (p < endp)
        {
            //printf("|%s|\n", p);
            if (p[0 .. 7] == "#define")
            {
                p += 7;
                nextToken();
                //printf("define %s\n", token.toChars());
                if (token.value == TOK.identifier)
                {
                    auto id = token.ident;
                    const params = *p == '(';
                    nextToken();

                    AST.Type t;

                    bool hasMinus;
                    if (token.value == TOK.min)
                    {
                        nextToken();
                        // Only allow integer and float literals after minus.
                        switch (token.value)
                        {
                            case TOK.int32Literal:
                            case TOK.uns32Literal:
                            case TOK.int64Literal:
                            case TOK.uns64Literal:
                            case TOK.float32Literal:
                            case TOK.float64Literal:
                            case TOK.float80Literal:
                            case TOK.imaginary32Literal:
                            case TOK.imaginary64Literal:
                            case TOK.imaginary80Literal:
                                hasMinus = true;
                                break;
                            default:
                                continue;
                        }
                    }

                Lswitch:
                    switch (token.value)
                    {
                        case TOK.endOfFile:     // #define identifier
                            ++p;
                            continue;

                        case TOK.int32Literal:
                        case TOK.charLiteral:       t = AST.Type.tint32;    goto Linteger;
                        case TOK.wcharLiteral:      t = AST.Type.tuns16;    goto Linteger;
                        case TOK.dcharLiteral:
                        case TOK.uns32Literal:      t = AST.Type.tuns32;    goto Linteger;
                        case TOK.int64Literal:      t = AST.Type.tint64;    goto Linteger;
                        case TOK.uns64Literal:      t = AST.Type.tuns64;    goto Linteger;

                        Linteger:
                            const intvalue = token.intvalue;
                            nextToken();
                            if (token.value == TOK.endOfFile)
                            {
                                /* Declare manifest constant:
                                 *  enum id = intvalue;
                                 */
                                AST.Expression e = new AST.IntegerExp(scanloc, intvalue, t);
                                if (hasMinus)
                                    e = new AST.NegExp(scanloc, e);
                                auto v = new AST.VarDeclaration(scanloc, t, id, new AST.ExpInitializer(scanloc, e), STC.manifest);
                                addSym(v);
                                ++p;
                                continue;
                            }
                            break;

                        case TOK.float32Literal:      t = AST.Type.tfloat32;     goto Lfloat;
                        case TOK.float64Literal:      t = AST.Type.tfloat64;     goto Lfloat;
                        case TOK.float80Literal:      t = AST.Type.tfloat80;     goto Lfloat;
                        case TOK.imaginary32Literal:  t = AST.Type.timaginary32; goto Lfloat;
                        case TOK.imaginary64Literal:  t = AST.Type.timaginary64; goto Lfloat;
                        case TOK.imaginary80Literal:  t = AST.Type.timaginary80; goto Lfloat;

                        Lfloat:
                            const floatvalue = token.floatvalue;
                            nextToken();
                            if (token.value == TOK.endOfFile)
                            {
                                /* Declare manifest constant:
                                 *  enum id = floatvalue;
                                 */
                                AST.Expression e = new AST.RealExp(scanloc, floatvalue, t);
                                if (hasMinus)
                                    e = new AST.NegExp(scanloc, e);
                                auto v = new AST.VarDeclaration(scanloc, t, id, new AST.ExpInitializer(scanloc, e), STC.manifest);
                                addSym(v);
                                ++p;
                                continue;
                            }
                            break;

                        case TOK.string_:
                            const str = token.ustring;
                            const len = token.len;
                            const postfix = token.postfix;
                            nextToken();
                            if (token.value == TOK.endOfFile)
                            {
                                /* Declare manifest constant:
                                 *  enum id = "string";
                                 */
                                AST.Expression e = new AST.StringExp(scanloc, str[0 .. len], len, 1, postfix);
                                auto v = new AST.VarDeclaration(scanloc, null, id, new AST.ExpInitializer(scanloc, e), STC.manifest);
                                addSym(v);
                                ++p;
                                continue;
                            }
                            break;

                        case TOK.leftParenthesis:
                        {
                            /* Look for:
                             *  #define ID ( expression )
                             * and rewrite it to a template function:
                             *  auto ID()() { return expression; }
                             */
                            if (params)
                                goto caseFunctionLike;        // version with parameters
                            nextToken();
                            eLatch.sawErrors = false;
                            auto exp = cparseExpression();
                            if (eLatch.sawErrors)   // parsing errors
                                break;              // abandon this #define
                            if (token.value != TOK.rightParenthesis)
                                break;
                            nextToken();
                            if (token.value != TOK.endOfFile)
                                break;
                            auto ret = new AST.ReturnStatement(exp.loc, exp);
                            auto parameterList = AST.ParameterList(new AST.Parameters(), VarArg.none, 0);
                            StorageClass stc = STC.auto_;
                            auto tf = new AST.TypeFunction(parameterList, null, LINK.d, stc);
                            auto fd = new AST.FuncDeclaration(exp.loc, exp.loc, id, stc, tf, 0);
                            fd.fbody = ret;
                            AST.Dsymbols* decldefs = new AST.Dsymbols();
                            decldefs.push(fd);
                            AST.TemplateParameters* tpl = new AST.TemplateParameters();
                            AST.Expression constraint = null;
                            auto tempdecl = new AST.TemplateDeclaration(exp.loc, id, tpl, constraint, decldefs, false);
                            addSym(tempdecl);
                            ++p;
                            continue;
                        }

                        caseFunctionLike:
                        {
                            /* Parse `( a, b ) expression`
                             * Create template function:
                             *    auto id(__MP1, __MP2)(__MP1 a, __MP1 b) { return expression; }
                             */
                            //printf("functionlike %s\n", id.toChars());

                            // Capture the parameter list
                            VarArg varargs = VarArg.none;
                            auto parameters = new AST.Parameters();
                            nextToken();        // skip past `(`
                        Lwhile:
                            while (1)
                            {
                                if (token.value == TOK.rightParenthesis)
                                    break;
                                if (token.value == TOK.dotDotDot)
                                {
                                    static if (0)       // variadic macros not supported yet
                                    {
                                        varargs = AST.VarArg.variadic;  // C-style variadics
                                        nextToken();
                                        if (token.value == TOK.rightParenthesis)
                                            break Lwhile;
                                    }
                                    break Lswitch;
                                }

                                if (token.value != TOK.identifier)
                                    break Lswitch;
                                auto param = new AST.Parameter(token.loc, 0, null, token.ident, null, null);
                                parameters.push(param);
                                nextToken();
                                if (token.value == TOK.comma)
                                {
                                    nextToken();
                                    continue;
                                }
                                break;
                            }
                            if (token.value != TOK.rightParenthesis)
                                break;

                            //auto pstart = p;
                            nextToken();
                            auto parameterList = AST.ParameterList(parameters, varargs, 0);
                            /* Create a type for each parameter. Add it to the template parameter list,
                             * and the parameter list.
                             */
                            auto tpl = new AST.TemplateParameters();
                            foreach (param; (*parameters)[])
                            {
                                auto idtype = Identifier.generateId("__MP");
                                auto loc = param.loc;
                                auto tp = new AST.TemplateTypeParameter(loc, idtype, null, null);
                                tpl.push(tp);

                                auto at = new AST.TypeIdentifier(loc, idtype);
                                param.type = at;
                            }

                            eLatch.sawErrors = false;
                            auto exp = cparseExpression();

                            //printf("exp: %s tok: %s\n", exp.toChars(), Token.toChars(token.value));
                            //printf("parsed: '%.*s'\n", cast(int)(p - pstart), pstart);
                            assert(symbols);

                            if (eLatch.sawErrors)   // parsing errors
                                break;              // abandon this #define

                            if (token.value != TOK.endOfFile)   // did not consume the entire line
                                break;

                            // Generate function
                            auto ret = new AST.ReturnStatement(exp.loc, exp);
                            StorageClass stc = STC.auto_;
                            auto tf = new AST.TypeFunction(parameterList, null, LINK.d, stc);
                            auto fd = new AST.FuncDeclaration(exp.loc, exp.loc, id, stc, tf, 0);
                            fd.fbody = ret;

                            // Wrap it in an eponymous template
                            AST.Dsymbols* decldefs = new AST.Dsymbols();
                            decldefs.push(fd);
                            auto tempdecl = new AST.TemplateDeclaration(exp.loc, id, tpl, null, decldefs, false);
                            addSym(tempdecl);

                            ++p;
                            continue;
                        }

                        default:
                            break;
                    }
                }
            }
            else if (p[0 .. 6] == "#undef")
            {
                p += 6;
                nextToken();
                //printf("undef %s\n", token.toChars());
                if (token.value == TOK.identifier)
                    removeSym(token.ident);
            }
            // scan to end of line
            while (*p)
                ++p;
            ++p; // advance to start of next line
            scanloc.linnum = scanloc.linnum + 1;
        }

        if (newSymbols.length)
        {
            assert(symbols, "symbols is null");
            symbols.reserve(newSymbols.length);

            foreach (sym; newSymbols)
                if (sym) // undefined entries are null
                    symbols.push(sym);
        }

        scanloc = scanlocSave;
        eSink = save;
        defines = buf;
    }

    //}
}
